mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-16 05:24:08 +02:00
Compare commits
481 Commits
2019-09-12
...
2022-01-20
Author | SHA1 | Date | |
---|---|---|---|
|
5df8bf956a | ||
|
a49767e71b | ||
|
0584fdddde | ||
|
0fdd281fb2 | ||
|
0e17282f60 | ||
|
0f1ec4a879 | ||
|
e9d3d7ba67 | ||
|
3cef35a432 | ||
|
2f10d2345a | ||
|
02a8ae4c62 | ||
|
084a1bcf19 | ||
|
418f951dd1 | ||
|
9dcce0ba1d | ||
|
607d9297ff | ||
|
c65feffb61 | ||
|
f259fa7f9f | ||
|
368a198321 | ||
|
590fdd9f9b | ||
|
799c93a3c6 | ||
|
2f957b6870 | ||
|
12ff697ab0 | ||
|
8530aa54f2 | ||
|
d0ef8aa71d | ||
|
59e77a9e51 | ||
|
37cb4091d4 | ||
|
fc51c6753d | ||
|
71cd15c35d | ||
|
df408fb8bc | ||
|
e545f43a67 | ||
|
ec55e99934 | ||
|
67e33186ce | ||
|
a0bbbd6978 | ||
|
2ee091665a | ||
|
35930ee4e4 | ||
|
e1290aa42c | ||
|
814711e3af | ||
|
28db707587 | ||
|
b48739d0ba | ||
|
b9d92150e1 | ||
|
b395fe2641 | ||
|
4bc534c80f | ||
|
071fdef599 | ||
|
ae6a3227b0 | ||
|
f469489b56 | ||
|
490f556783 | ||
|
192f0278d2 | ||
|
2b634002f2 | ||
|
42379071e9 | ||
|
fd54042ef3 | ||
|
3764348b76 | ||
|
0ba0e2de4e | ||
|
4187d8f4cf | ||
|
1c6532a9d0 | ||
|
547829f971 | ||
|
970bdd45f9 | ||
|
b86ed70376 | ||
|
9254d14f50 | ||
|
8f98e07979 | ||
|
8d0fc54e4d | ||
|
bdf15c3ce0 | ||
|
927b08ed00 | ||
|
87b3aaa550 | ||
|
c445ba6ebb | ||
|
793c55f43d | ||
|
11be390e65 | ||
|
ba5baaf7c9 | ||
|
f0ddd686e3 | ||
|
f6b9864bdd | ||
|
64b7c54bc8 | ||
|
d5a010adcd | ||
|
2a609b39bd | ||
|
dacc586dca | ||
|
8bcf4ebfbf | ||
|
cb111a3ebd | ||
|
42e40e2823 | ||
|
1ddce120ae | ||
|
927cb17dbf | ||
|
ccb2e64fd0 | ||
|
a26408594b | ||
|
324932642d | ||
|
455b5e09a1 | ||
|
bcc15228d8 | ||
|
68d9e2ff24 | ||
|
a5d33615f5 | ||
|
8f634eb4a1 | ||
|
677e4974d1 | ||
|
10c5259493 | ||
|
cb3c055df9 | ||
|
036a3ad245 | ||
|
69ce8106ce | ||
|
8a30480a45 | ||
|
f36832b66e | ||
|
f3f934ed8b | ||
|
7c46c64242 | ||
|
bf1773ed8b | ||
|
4529e3699a | ||
|
f5f0b77805 | ||
|
3637777070 | ||
|
c673917aca | ||
|
877707f7b2 | ||
|
2689f5f7fa | ||
|
bf1cb8fadc | ||
|
716f5ddc0e | ||
|
0ee549f468 | ||
|
4a1e26fd07 | ||
|
e14f647075 | ||
|
34489431b4 | ||
|
126cf1a7fa | ||
|
3050f0ae70 | ||
|
cabf7a748a | ||
|
84450e7e8d | ||
|
971cd9ba39 | ||
|
9fa782105d | ||
|
cb7f5b057f | ||
|
a8d1acfdad | ||
|
b5d9742a21 | ||
|
7dd1a7dccc | ||
|
1f6ad000ce | ||
|
398e175fe0 | ||
|
0de2db853f | ||
|
9399ebb2c6 | ||
|
606972dd3c | ||
|
ecaae735d9 | ||
|
5598fef3cf | ||
|
9c99a1a9c1 | ||
|
75cc52a62c | ||
|
973e49d93e | ||
|
c580219627 | ||
|
a18af3952d | ||
|
15907e2bbd | ||
|
459adf4790 | ||
|
d38bc18232 | ||
|
eec1163fb9 | ||
|
b074abcc0d | ||
|
2ae9793f2c | ||
|
f02d80e141 | ||
|
44e01a4282 | ||
|
63d257d9d0 | ||
|
37cd071453 | ||
|
17f9c44bfc | ||
|
28aaf59007 | ||
|
e8d241e8c9 | ||
|
2b793f04de | ||
|
655e02e3fe | ||
|
a4bd04310f | ||
|
e48617530d | ||
|
3585575d68 | ||
|
e79a02ac2e | ||
|
63ebf5ceec | ||
|
378f78d6eb | ||
|
d7ba7782f3 | ||
|
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 | ||
|
0705a2e7bb | ||
|
84616f53bf | ||
|
a981450ae0 | ||
|
d39741c296 | ||
|
3179c1e884 | ||
|
c9e5f6c9dd | ||
|
6b6974d115 | ||
|
96e58d4c94 | ||
|
f0363ba03b | ||
|
90147fc45c | ||
|
a3b4bd2d08 | ||
|
e102353ab8 | ||
|
a54eb88ee1 | ||
|
1584636e5b | ||
|
fe83d763a3 | ||
|
480694e819 | ||
|
8697e1e1a2 | ||
|
1ab7e493a8 | ||
|
e5303efba3 | ||
|
5bd07723ad | ||
|
00dbde2c24 | ||
|
a00e75b71c | ||
|
f040e4dc9c | ||
|
182e9e7b41 | ||
|
275662b8d4 | ||
|
f52eb43f8c | ||
|
2450f80823 | ||
|
45287e6853 | ||
|
830f57f607 | ||
|
6a90a9d33f | ||
|
46b9879c08 | ||
|
1343dbe97a | ||
|
2175a4d08b | ||
|
ad661c4c91 | ||
|
ba8c4623ed | ||
|
ba43c87952 | ||
|
595b87946d | ||
|
99d4e1a43d | ||
|
477de4e2df | ||
|
246470da18 | ||
|
df9f7eb778 | ||
|
375831f516 | ||
|
e518936be7 | ||
|
583dfb4958 | ||
|
2de45b163e | ||
|
48b0164676 | ||
|
4b3c3c58d2 | ||
|
60768b4885 | ||
|
02dd778124 | ||
|
5b63121e92 | ||
|
49019a843f | ||
|
d65714fa47 | ||
|
8161829ad5 | ||
|
7f35fc9f6b | ||
|
3bc8c9468a | ||
|
1df3598a74 | ||
|
5f64fe2516 | ||
|
50eee7e7b3 | ||
|
c0df9815c7 | ||
|
46d5895d1d | ||
|
7c16aaf303 | ||
|
cdc1d9c9ba | ||
|
6bc83310b9 | ||
|
c8d5c85c76 | ||
|
d1e4bd7285 | ||
|
1022b5fdf9 | ||
|
e8536ac1b2 | ||
|
a0afe36d56 | ||
|
0b80f9d61c | ||
|
424075981f | ||
|
c334df91ec | ||
|
f2346fb33e | ||
|
8a21fd1476 | ||
|
2ac44172ac | ||
|
4c78721f03 | ||
|
04be85996d | ||
|
59be6bded2 | ||
|
46873e14fe | ||
|
0f01cc97a4 | ||
|
a70e00a76d | ||
|
f0260c62c3 | ||
|
fc5a1526ca | ||
|
4c0e234479 | ||
|
0eab63d728 | ||
|
b0884e9158 | ||
|
b4581418d4 | ||
|
af1566f40d | ||
|
529e0d0cca | ||
|
a3532804ac | ||
|
8c19146d29 | ||
|
2a3d5865ad | ||
|
4d36c9dc30 | ||
|
a2e47a88c3 | ||
|
b09f50853f | ||
|
9b5bf565b3 | ||
|
5cc956367f | ||
|
548e28249b | ||
|
684c69b0cd | ||
|
3dae4e0801 | ||
|
4622d9be1e | ||
|
76183dcd44 | ||
|
50b234d893 | ||
|
af48f36fd2 | ||
|
7f6ca23e8f | ||
|
1daef22a3d | ||
|
c694810d9a | ||
|
f12f6a2dba | ||
|
b1be45df6c |
61
.github/workflows/dockerbuild.yml
vendored
Normal file
61
.github/workflows/dockerbuild.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Build Image on Commit and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- '20*'
|
||||
|
||||
env:
|
||||
DOCKERHUB_SLUG: rssbridge/rss-bridge
|
||||
GHCR_SLUG: ghcr.io/rss-bridge/rss-bridge
|
||||
|
||||
jobs:
|
||||
bake:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3.5.0
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_SLUG }}
|
||||
${{ env.GHCR_SLUG }}
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=sha
|
||||
type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/20') }}
|
||||
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/20') }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/bake-action@v1.6.0
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
${{ steps.docker_meta.outputs.bake-file }}
|
||||
targets: image-all
|
||||
push: true
|
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-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.1', '7.2', '7.3', '7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpcs
|
||||
- run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
|
||||
|
||||
phpcompatibility:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.1', '7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require dealerdirect/phpcodesniffer-composer-installer
|
||||
- run: composer global require phpcompatibility/php-compatibility
|
||||
- run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p
|
35
.github/workflows/tests.yml
vendored
Normal file
35
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
# TODO: return back when fixed https://github.com/RSS-Bridge/rss-bridge/issues/2391
|
||||
# phpunit7:
|
||||
# runs-on: ubuntu-20.04
|
||||
# strategy:
|
||||
# matrix:
|
||||
# php-versions: ['7.1', '7.2', '7.3']
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: shivammathur/setup-php@v2
|
||||
# with:
|
||||
# php-version: ${{ matrix.php-versions }}
|
||||
# - run: composer global require phpunit/phpunit ^7
|
||||
# - run: phpunit --configuration=phpunit.xml --include-path=lib/
|
||||
|
||||
phpunit8:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4', '8.0', '8.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require phpunit/phpunit ^8
|
||||
- run: phpunit --configuration=phpunit.xml --include-path=lib/
|
46
.travis.yml
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
|
21
Dockerfile
21
Dockerfile
@@ -1,11 +1,24 @@
|
||||
FROM php:7-apache
|
||||
|
||||
LABEL description="RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one."
|
||||
LABEL repository="https://github.com/RSS-Bridge/rss-bridge"
|
||||
LABEL website="https://github.com/RSS-Bridge/rss-bridge"
|
||||
|
||||
ENV APACHE_DOCUMENT_ROOT=/app
|
||||
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
|
||||
&& apt-get --yes update && apt-get --yes install libxml2-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!/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
|
||||
|
||||
COPY --chown=www-data:www-data ./ /app/
|
||||
COPY --chown=www-data:www-data ./ /app/
|
||||
|
||||
CMD ["/app/docker-entrypoint.sh"]
|
||||
|
108
README.md
108
README.md
@@ -1,8 +1,13 @@
|
||||

|
||||
===
|
||||
[](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://web.libera.chat/#rssbridge)
|
||||
[](https://matrix.to/#/#rssbridge:libera.chat)
|
||||
[](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 which don't have one. It can be used on webservers or as stand alone application in CLI mode.
|
||||
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode.
|
||||
|
||||
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
|
||||
|
||||
@@ -13,11 +18,11 @@ Supported sites/pages (examples)
|
||||
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
|
||||
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
|
||||
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
|
||||
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
|
||||
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/) (There is an [issue](https://github.com/RSS-Bridge/rss-bridge/issues/2047) for public instances)
|
||||
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
|
||||
* `GoogleSearch` : Most recent results from Google Search
|
||||
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
|
||||
* `Instagram`: Most recent photos from an Instagram user
|
||||
* `Instagram`: Most recent photos from an Instagram user (There is an [issue](https://github.com/RSS-Bridge/rss-bridge/issues/1891) for public instances)
|
||||
* `OpenClassrooms`: Lastest tutorials from [fr.openclassrooms.com](http://fr.openclassrooms.com/)
|
||||
* `Pinterest`: Most recent photos from user or search
|
||||
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
|
||||
@@ -57,7 +62,7 @@ RSS-Bridge hashtag (#rss-bridge) search on Twitter, in Atom format (as displayed
|
||||
Requirements
|
||||
===
|
||||
|
||||
RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
|
||||
RSS-Bridge requires PHP 7.1 or higher with following extensions enabled:
|
||||
|
||||
- [`openssl`](https://secure.php.net/manual/en/book.openssl.php)
|
||||
- [`libxml`](https://secure.php.net/manual/en/book.libxml.php)
|
||||
@@ -65,6 +70,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)
|
||||
@@ -76,7 +82,7 @@ RSS-Bridge allows you to take full control over which bridges are displayed to t
|
||||
|
||||
Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting)
|
||||
|
||||
**Notice**: By default RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
|
||||
**Notice**: By default, RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
|
||||
|
||||
Deploy
|
||||
===
|
||||
@@ -107,119 +113,196 @@ We are RSS-Bridge community, a group of developers continuing the project initia
|
||||
**Contributors** (sorted alphabetically):
|
||||
<!--
|
||||
Use this script to generate the list automatically (using the GitHub API):
|
||||
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
./contrib/prepare_release/fetch_contributors.php
|
||||
-->
|
||||
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [adamchainz](https://github.com/adamchainz)
|
||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||
* [akirk](https://github.com/akirk)
|
||||
* [Albirew](https://github.com/Albirew)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alex73](https://github.com/alex73)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||
* [arnd-s](https://github.com/arnd-s)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [AxorPL](https://github.com/AxorPL)
|
||||
* [ayacoo](https://github.com/ayacoo)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [azdkj532](https://github.com/azdkj532)
|
||||
* [b1nj](https://github.com/b1nj)
|
||||
* [benasse](https://github.com/benasse)
|
||||
* [Binnette](https://github.com/Binnette)
|
||||
* [Bockiii](https://github.com/Bockiii)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
* [Chouchen](https://github.com/Chouchen)
|
||||
* [ckiw](https://github.com/ckiw)
|
||||
* [cn-tools](https://github.com/cn-tools)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [couraudt](https://github.com/couraudt)
|
||||
* [csisoap](https://github.com/csisoap)
|
||||
* [da2x](https://github.com/da2x)
|
||||
* [dabenzel](https://github.com/dabenzel)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [dawidsowa](https://github.com/dawidsowa)
|
||||
* [DevonHess](https://github.com/DevonHess)
|
||||
* [dhuschde](https://github.com/dhuschde)
|
||||
* [disk0x](https://github.com/disk0x)
|
||||
* [DJCrashdummy](https://github.com/DJCrashdummy)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [DnAp](https://github.com/DnAp)
|
||||
* [dominik-th](https://github.com/dominik-th)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
|
||||
* [drego85](https://github.com/drego85)
|
||||
* [drklee3](https://github.com/drklee3)
|
||||
* [dvikan](https://github.com/dvikan)
|
||||
* [em92](https://github.com/em92)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [f0086](https://github.com/f0086)
|
||||
* [fanch317](https://github.com/fanch317)
|
||||
* [fatuuse](https://github.com/fatuuse)
|
||||
* [fivefilters](https://github.com/fivefilters)
|
||||
* [floviolleau](https://github.com/floviolleau)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [fmachen](https://github.com/fmachen)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [fulmeek](https://github.com/fulmeek)
|
||||
* [ggiessen](https://github.com/ggiessen)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [girlpunk](https://github.com/girlpunk)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [gloony](https://github.com/gloony)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [gsantner](https://github.com/gsantner)
|
||||
* [guigot](https://github.com/guigot)
|
||||
* [hollowleviathan](https://github.com/hollowleviathan)
|
||||
* [hpacleb](https://github.com/hpacleb)
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [husim0](https://github.com/husim0)
|
||||
* [IceWreck](https://github.com/IceWreck)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [jacquesh](https://github.com/jacquesh)
|
||||
* [JasonGhent](https://github.com/JasonGhent)
|
||||
* [jcgoette](https://github.com/jcgoette)
|
||||
* [jdesgats](https://github.com/jdesgats)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [JimDog546](https://github.com/JimDog546)
|
||||
* [jNullj](https://github.com/jNullj)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [johnnygroovy](https://github.com/johnnygroovy)
|
||||
* [killruana](https://github.com/killruana)
|
||||
* [johnpc](https://github.com/johnpc)
|
||||
* [joni1993](https://github.com/joni1993)
|
||||
* [KamaleiZestri](https://github.com/KamaleiZestri)
|
||||
* [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)
|
||||
* [laBecasse](https://github.com/laBecasse)
|
||||
* [lagaisse](https://github.com/lagaisse)
|
||||
* [lalannev](https://github.com/lalannev)
|
||||
* [Leomaradan](https://github.com/Leomaradan)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [Leomaradan](https://github.com/Leomaradan)
|
||||
* [leyrer](https://github.com/leyrer)
|
||||
* [liamka](https://github.com/liamka)
|
||||
* [Limero](https://github.com/Limero)
|
||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||
* [lorenzos](https://github.com/lorenzos)
|
||||
* [lukasklinger](https://github.com/lukasklinger)
|
||||
* [m0zes](https://github.com/m0zes)
|
||||
* [Mar-Koeh](https://github.com/Mar-Koeh)
|
||||
* [marcus-at-localhost](https://github.com/marcus-at-localhost)
|
||||
* [marius851000](https://github.com/marius851000)
|
||||
* [matthewseal](https://github.com/matthewseal)
|
||||
* [mcbyte-it](https://github.com/mcbyte-it)
|
||||
* [mdemoss](https://github.com/mdemoss)
|
||||
* [melangue](https://github.com/melangue)
|
||||
* [metaMMA](https://github.com/metaMMA)
|
||||
* [mibe](https://github.com/mibe)
|
||||
* [mightymt](https://github.com/mightymt)
|
||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||
* [Monocularity](https://github.com/Monocularity)
|
||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mschwld](https://github.com/mschwld)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [Niehztog](https://github.com/Niehztog)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [ObsidianWitch](https://github.com/ObsidianWitch)
|
||||
* [obsiwitch](https://github.com/obsiwitch)
|
||||
* [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)
|
||||
* [sal0max](https://github.com/sal0max)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [shutosg](https://github.com/shutosg)
|
||||
* [simon816](https://github.com/simon816)
|
||||
* [Simounet](https://github.com/Simounet)
|
||||
* [somini](https://github.com/somini)
|
||||
* [SpangleLabs](https://github.com/SpangleLabs)
|
||||
* [squeek502](https://github.com/squeek502)
|
||||
* [stjohnjohnson](https://github.com/stjohnjohnson)
|
||||
* [Stopka](https://github.com/Stopka)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
* [sublimz](https://github.com/sublimz)
|
||||
* [sunchaserinfo](https://github.com/sunchaserinfo)
|
||||
* [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
* [sysadminstory](https://github.com/sysadminstory)
|
||||
* [t0stiman](https://github.com/t0stiman)
|
||||
* [tameroski](https://github.com/tameroski)
|
||||
* [teromene](https://github.com/teromene)
|
||||
* [tgkenney](https://github.com/tgkenney)
|
||||
* [thefranke](https://github.com/thefranke)
|
||||
* [ThePadawan](https://github.com/ThePadawan)
|
||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||
* [theScrabi](https://github.com/theScrabi)
|
||||
* [thezeroalpha](https://github.com/thezeroalpha)
|
||||
* [timendum](https://github.com/timendum)
|
||||
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
|
||||
* [triatic](https://github.com/triatic)
|
||||
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
|
||||
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||
* [wtuuju](https://github.com/wtuuju)
|
||||
* [xurxof](https://github.com/xurxof)
|
||||
* [yamanq](https://github.com/yamanq)
|
||||
* [yardenac](https://github.com/yardenac)
|
||||
* [ymeister](https://github.com/ymeister)
|
||||
* [yue-dongchen](https://github.com/yue-dongchen)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
|
||||
Licenses
|
||||
@@ -229,6 +312,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)
|
||||
|
||||
@@ -250,6 +334,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.
|
||||
|
136
actions/ConnectivityAction.php
Normal file
136
actions/ConnectivityAction.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if the website for a given bridge is reachable.
|
||||
*
|
||||
* **Remarks**
|
||||
* - This action is only available in debug mode.
|
||||
* - Returns the bridge status as Json-formatted string.
|
||||
* - Returns an error if the bridge is not whitelisted.
|
||||
* - Returns a responsive web page that automatically checks all whitelisted
|
||||
* bridges (using JavaScript) if no bridge is specified.
|
||||
*/
|
||||
class ConnectivityAction extends ActionAbstract {
|
||||
public function execute() {
|
||||
|
||||
if(!Debug::isEnabled()) {
|
||||
returnError('This action is only available in debug mode!');
|
||||
}
|
||||
|
||||
if(!isset($this->userData['bridge'])) {
|
||||
$this->returnEntryPage();
|
||||
return;
|
||||
}
|
||||
|
||||
$bridgeName = $this->userData['bridge'];
|
||||
|
||||
$this->reportBridgeConnectivity($bridgeName);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report about the bridge connectivity status and sends it back
|
||||
* to the user.
|
||||
*
|
||||
* The report is generated as Json-formatted string in the format
|
||||
* {
|
||||
* "bridge": "<bridge-name>",
|
||||
* "successful": true/false
|
||||
* }
|
||||
*
|
||||
* @param string $bridgeName Name of the bridge to generate the report for
|
||||
* @return void
|
||||
*/
|
||||
private function reportBridgeConnectivity($bridgeName) {
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
if(!$bridgeFac->isWhitelisted($bridgeName)) {
|
||||
header('Content-Type: text/html');
|
||||
returnServerError('Bridge is not whitelisted!');
|
||||
}
|
||||
|
||||
header('Content-Type: text/json');
|
||||
|
||||
$retVal = array(
|
||||
'bridge' => $bridgeName,
|
||||
'successful' => false,
|
||||
'http_code' => 200,
|
||||
);
|
||||
|
||||
$bridge = $bridgeFac->create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
echo json_encode($retVal);
|
||||
return;
|
||||
}
|
||||
|
||||
$curl_opts = array(
|
||||
CURLOPT_CONNECTTIMEOUT => 5
|
||||
);
|
||||
|
||||
try {
|
||||
$reply = getContents($bridge::URI, array(), $curl_opts, true);
|
||||
|
||||
if($reply) {
|
||||
$retVal['successful'] = true;
|
||||
if (isset($reply['header'])) {
|
||||
if (strpos($reply['header'], 'HTTP/1.1 301 Moved Permanently') !== false) {
|
||||
$retVal['http_code'] = 301;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
$retVal['successful'] = false;
|
||||
}
|
||||
|
||||
echo json_encode($retVal);
|
||||
|
||||
}
|
||||
|
||||
private function returnEntryPage() {
|
||||
echo <<<EOD
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="static/bootstrap.min.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="static/connectivity.css">
|
||||
<script src="static/connectivity.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content" class="container">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<div id="status-message" class="sticky-top alert alert-primary alert-dismissible fade show" role="alert">
|
||||
<i id="status-icon" class="fas fa-sync"></i>
|
||||
<span>...</span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="stopConnectivityChecks()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="search" onkeyup="search()" placeholder="Search for bridge..">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOD;
|
||||
}
|
||||
}
|
@@ -12,6 +12,15 @@
|
||||
*/
|
||||
|
||||
class DisplayAction extends ActionAbstract {
|
||||
private function get_return_code($error) {
|
||||
$returnCode = $error->getCode();
|
||||
if ($returnCode === 301 || $returnCode === 302) {
|
||||
# Don't pass redirect codes to the exterior
|
||||
$returnCode = 508;
|
||||
}
|
||||
return $returnCode;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
|
||||
|
||||
@@ -122,6 +131,7 @@ class DisplayAction extends ActionAbstract {
|
||||
|
||||
try {
|
||||
$bridge->setDatas($bridge_params);
|
||||
$bridge->loadConfiguration();
|
||||
$bridge->collectData();
|
||||
|
||||
$items = $bridge->getItems();
|
||||
@@ -141,68 +151,83 @@ class DisplayAction extends ActionAbstract {
|
||||
$infos = array(
|
||||
'name' => $bridge->getName(),
|
||||
'uri' => $bridge->getURI(),
|
||||
'donationUri' => $bridge->getDonationURI(),
|
||||
'icon' => $bridge->getIcon()
|
||||
);
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = new \FeedItem();
|
||||
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
|
||||
if(Configuration::getConfig('error', 'output') === 'feed') {
|
||||
$item = new \FeedItem();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
// Error 0 is a special case (i.e. "trying to get property of non-object")
|
||||
if($e->getCode() === 0) {
|
||||
$item->setTitle(
|
||||
'Bridge encountered an unexpected situation! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
} else {
|
||||
$item->setTitle(
|
||||
'Bridge returned error '
|
||||
. $e->getCode()
|
||||
. '! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
// Error 0 is a special case (i.e. "trying to get property of non-object")
|
||||
if($e->getCode() === 0) {
|
||||
$item->setTitle(
|
||||
'Bridge encountered an unexpected situation! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
} else {
|
||||
$item->setTitle(
|
||||
'Bridge returned error '
|
||||
. $e->getCode()
|
||||
. '! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
}
|
||||
|
||||
$item->setURI(
|
||||
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?'
|
||||
. http_build_query($this->userData)
|
||||
);
|
||||
|
||||
$item->setTimestamp(time());
|
||||
$item->setContent(buildBridgeException($e, $bridge));
|
||||
|
||||
$items[] = $item;
|
||||
} elseif(Configuration::getConfig('error', 'output') === 'http') {
|
||||
header('Content-Type: text/html', true, $this->get_return_code($e));
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
}
|
||||
|
||||
$item->setURI(
|
||||
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?'
|
||||
. http_build_query($this->userData)
|
||||
);
|
||||
|
||||
$item->setTimestamp(time());
|
||||
$item->setContent(buildBridgeException($e, $bridge));
|
||||
|
||||
$items[] = $item;
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = new \FeedItem();
|
||||
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
|
||||
if(Configuration::getConfig('error', 'output') === 'feed') {
|
||||
$item = new \FeedItem();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
$item->setURI(
|
||||
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?'
|
||||
. http_build_query($this->userData)
|
||||
);
|
||||
$item->setURI(
|
||||
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?'
|
||||
. http_build_query($this->userData)
|
||||
);
|
||||
|
||||
$item->setTitle(
|
||||
'Bridge returned error '
|
||||
. $e->getCode()
|
||||
. '! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
$item->setTimestamp(time());
|
||||
$item->setContent(buildBridgeException($e, $bridge));
|
||||
$item->setTitle(
|
||||
'Bridge returned error '
|
||||
. $e->getCode()
|
||||
. '! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
$item->setTimestamp(time());
|
||||
$item->setContent(buildBridgeException($e, $bridge));
|
||||
|
||||
$items[] = $item;
|
||||
$items[] = $item;
|
||||
} elseif(Configuration::getConfig('error', 'output') === 'http') {
|
||||
header('Content-Type: text/html', true, $this->get_return_code($e));
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store data in cache
|
||||
|
@@ -39,6 +39,7 @@ class ListAction extends ActionAbstract {
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => $status,
|
||||
'uri' => $bridge->getURI(),
|
||||
'donationUri' => $bridge->getDonationURI(),
|
||||
'name' => $bridge->getName(),
|
||||
'icon' => $bridge->getIcon(),
|
||||
'parameters' => $bridge->getParameters(),
|
||||
|
45
bridges/ABCNewsBridge.php
Normal file
45
bridges/ABCNewsBridge.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class ABCNewsBridge extends BridgeAbstract {
|
||||
const NAME = 'ABC News Bridge';
|
||||
const URI = 'https://www.abc.net.au';
|
||||
const DESCRIPTION = 'Topics of the Australian Broadcasting Corporation';
|
||||
const MAINTAINER = 'yue-dongchen';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'topic' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Region',
|
||||
'title' => 'Choose state',
|
||||
'values' => array(
|
||||
'ACT' => 'act',
|
||||
'NSW' => 'nsw',
|
||||
'NT' => 'nt',
|
||||
'QLD' => 'qld',
|
||||
'SA' => 'sa',
|
||||
'TAS' => 'tas',
|
||||
'VIC' => 'vic',
|
||||
'WA' => 'wa'
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = 'https://www.abc.net.au/news/' . $this->getInput('topic');
|
||||
$html = getSimpleHTMLDOM($url)->find('.YAJzu._26IxR._2kxNB._3BZxh', 0);
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
foreach($html->find('._2H7Su') as $article) {
|
||||
$item = array();
|
||||
|
||||
$title = $article->find('._3T9Id.fmhNa.nsZdE._2c2Zy._1tOey._3EOTW', 0);
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['uri'] = $title->href;
|
||||
$item['content'] = $article->find('.rMkro._1cBaI._3PhF6._10YQT._1yL-m', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($article->find('time', 0)->datetime);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -37,8 +37,7 @@ class AO3Bridge extends BridgeAbstract {
|
||||
// Feed for lists of works (e.g. recent works, search results, filtered tags,
|
||||
// bookmarks, series, collections).
|
||||
private function collectList($url) {
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('could not request AO3');
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('.index.group > li') as $element) {
|
||||
@@ -65,8 +64,7 @@ class AO3Bridge extends BridgeAbstract {
|
||||
// Feed for recent chapters of a specific work.
|
||||
private function collectWork($id) {
|
||||
$url = self::URI . "/works/$id/navigate";
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('could not request AO3');
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$this->title = $html->find('h2 a', 0)->plaintext;
|
||||
|
91
bridges/ARDMediathekBridge.php
Normal file
91
bridges/ARDMediathekBridge.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
class ARDMediathekBridge extends BridgeAbstract {
|
||||
const NAME = 'ARD-Mediathek Bridge';
|
||||
const URI = 'https://www.ardmediathek.de';
|
||||
const DESCRIPTION = 'Feed of any series in the ARD-Mediathek, specified by its path';
|
||||
const MAINTAINER = 'yue-dongchen';
|
||||
/*
|
||||
* Number of Items to be requested from ARDmediathek API
|
||||
* 12 has been observed on the wild
|
||||
* 29 is the highest successfully tested value
|
||||
* More Items could be fetched via pagination
|
||||
* The JSON-field pagination holds more information on that
|
||||
* @const PAGESIZE number of requested items
|
||||
*/
|
||||
const PAGESIZE = 29;
|
||||
/*
|
||||
* The URL Prefix of the (Webapp-)API
|
||||
* @const APIENDPOINT https-URL of the used endpoint
|
||||
*/
|
||||
const APIENDPOINT = 'https://api.ardmediathek.de/page-gateway/widgets/ard/asset/';
|
||||
/*
|
||||
* The URL prefix of the video link
|
||||
* URLs from the webapp include a slug containing titles of show, episode, and tv station.
|
||||
* It seems to work without that.
|
||||
* @const VIDEOLINKPREFIX https-URL prefix of video links
|
||||
*/
|
||||
const VIDEOLINKPREFIX = 'https://www.ardmediathek.de/video/';
|
||||
/*
|
||||
* The requested width of the preview image
|
||||
* 432 has been observed on the wild
|
||||
* The webapp seems to also compute and add the height value
|
||||
* It seems to works without that.
|
||||
* @const IMAGEWIDTH width in px of the preview image
|
||||
*/
|
||||
const IMAGEWIDTH = 432;
|
||||
/*
|
||||
* Placeholder that will be replace by IMAGEWIDTH in the preview image URL
|
||||
* @const IMAGEWIDTHPLACEHOLDER
|
||||
*/
|
||||
const IMAGEWIDTHPLACEHOLDER = '{width}';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'path' => array(
|
||||
'name' => 'Show Link or ID',
|
||||
'required' => true,
|
||||
'title' => 'Link to the show page or just its alphanumeric suffix',
|
||||
'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
|
||||
$pathComponents = explode('/', $this->getInput('path'));
|
||||
if (empty($pathComponents)) {
|
||||
returnClientError('Path may not be empty');
|
||||
}
|
||||
if (count($pathComponents) < 2) {
|
||||
$showID = $pathComponents[0];
|
||||
} else {
|
||||
$lastKey = count($pathComponents) - 1;
|
||||
$showID = $pathComponents[$lastKey];
|
||||
if (strlen($showID) === 0) {
|
||||
$showID = $pathComponents[$lastKey - 1];
|
||||
}
|
||||
}
|
||||
|
||||
$url = SELF::APIENDPOINT . $showID . '/?pageSize=' . SELF::PAGESIZE;
|
||||
$rawJSON = getContents($url);
|
||||
$processedJSON = json_decode($rawJSON);
|
||||
|
||||
foreach($processedJSON->teasers as $video) {
|
||||
$item = array();
|
||||
// there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId
|
||||
$item['uri'] = SELF::VIDEOLINKPREFIX . $video->id . '/';
|
||||
// there is also ->mediumTitle and ->shortTitle
|
||||
$item['title'] = $video->longTitle;
|
||||
// in the test, aspect16x9 was the only child of images, not sure whether that is always true
|
||||
$item['enclosures'] = array(
|
||||
str_replace(SELF::IMAGEWIDTHPLACEHOLDER, SELF::IMAGEWIDTH, $video->images->aspect16x9->src)
|
||||
);
|
||||
$item['content'] = '<img src="' . $item['enclosures'][0] . '" /><p>';
|
||||
$item['timestamp'] = $video->broadcastedOn;
|
||||
$item['uid'] = $video->id;
|
||||
$item['author'] = $video->publicationService->name;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
55
bridges/ASRockNewsBridge.php
Normal file
55
bridges/ASRockNewsBridge.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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');
|
||||
|
||||
$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);
|
||||
|
||||
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
|
||||
|
||||
$contents = $articlePageHtml->find('div.Contents', 0);
|
||||
|
||||
$item['uri'] = $articlePath;
|
||||
$item['title'] = $contents->find('h3', 0)->innertext;
|
||||
|
||||
$contents->find('h3', 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;
|
||||
}
|
||||
}
|
||||
}
|
72
bridges/AlbionOnlineBridge.php
Normal file
72
bridges/AlbionOnlineBridge.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?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);
|
||||
|
||||
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);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
return $html->find('div.small-12.columns', 1)->innertext;
|
||||
}
|
||||
}
|
@@ -8,14 +8,25 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'Bridge for allocine.fr';
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
'name' => 'category',
|
||||
'name' => 'Emission',
|
||||
'type' => 'list',
|
||||
'exampleValue' => 'Faux Raccord',
|
||||
'title' => 'Select your category',
|
||||
'title' => 'Sélectionner l\'emission',
|
||||
'values' => array(
|
||||
'Faux Raccord' => 'faux-raccord',
|
||||
'Top 5' => 'top-5',
|
||||
'Tueurs en Séries' => 'tueurs-en-serie'
|
||||
'Fanzone' => 'fanzone',
|
||||
'Game In Ciné' => 'game-in-cine',
|
||||
'Pour la faire courte' => 'pour-la-faire-courte',
|
||||
'Home Cinéma' => 'home-cinema',
|
||||
'PILS - Par Ici Les Sorties' => 'pils-par-ici-les-sorties',
|
||||
'AlloCiné : l\'émission, sur LeStream' => 'allocine-lemission-sur-lestream',
|
||||
'Give Me Five' => 'give-me-five',
|
||||
'Aviez-vous remarqué ?' => 'aviez-vous-remarque',
|
||||
'Et paf, il est mort' => 'et-paf-il-est-mort',
|
||||
'The Big Fan Theory' => 'the-big-fan-theory',
|
||||
'Clichés' => 'cliches',
|
||||
'Complètement...' => 'completement',
|
||||
'#Fun Facts' => 'fun-facts',
|
||||
'Origin Story' => 'origin-story',
|
||||
)
|
||||
)
|
||||
));
|
||||
@@ -23,19 +34,30 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
|
||||
switch($this->getInput('category')) {
|
||||
case 'faux-raccord':
|
||||
$uri = static::URI . 'video/programme-12284/saison-32180/';
|
||||
break;
|
||||
case 'top-5':
|
||||
$uri = static::URI . 'video/programme-12299/saison-29561/';
|
||||
break;
|
||||
case 'tueurs-en-serie':
|
||||
$uri = static::URI . 'video/programme-12286/saison-22938/';
|
||||
break;
|
||||
}
|
||||
$categories = array(
|
||||
'faux-raccord' => 'video/programme-12284/saison-37054/',
|
||||
'fanzone' => 'video/programme-12298/saison-37059/',
|
||||
'game-in-cine' => 'video/programme-12288/saison-22971/',
|
||||
'pour-la-faire-courte' => 'video/programme-20960/saison-29678/',
|
||||
'home-cinema' => 'video/programme-12287/saison-34703/',
|
||||
'pils-par-ici-les-sorties' => 'video/programme-25789/saison-37253/',
|
||||
'allocine-lemission-sur-lestream' => 'video/programme-25123/saison-36067/',
|
||||
'give-me-five' => 'video/programme-21919/saison-34518/',
|
||||
'aviez-vous-remarque' => 'video/programme-19518/saison-37084/',
|
||||
'et-paf-il-est-mort' => 'video/programme-25113/saison-36657/',
|
||||
'the-big-fan-theory' => 'video/programme-20403/saison-37419/',
|
||||
'cliches' => 'video/programme-24834/saison-35591/',
|
||||
'completement' => 'video/programme-23859/saison-34102/',
|
||||
'fun-facts' => 'video/programme-23040/saison-32686/',
|
||||
'origin-story' => 'video/programme-25667/saison-37041/'
|
||||
);
|
||||
|
||||
return $uri;
|
||||
$category = $this->getInput('category');
|
||||
if(array_key_exists($category, $categories)) {
|
||||
return static::URI . $categories[$category];
|
||||
} else {
|
||||
returnClientError('Emission inconnue');
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
@@ -55,31 +77,30 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI() . ' !');
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$category = array_search(
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
||||
foreach($html->find('.media-meta-list figure.media-meta-fig') as $element) {
|
||||
foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('div.titlebar h3.title a', 0);
|
||||
$content = trim($element->innertext);
|
||||
$figCaption = strpos($content, $category);
|
||||
$title = $element->find('a[class*=meta-title-link]', 0);
|
||||
$content = trim($element->outertext);
|
||||
|
||||
if($figCaption !== false) {
|
||||
$content = str_replace('src="/', 'src="' . static::URI, $content);
|
||||
$content = str_replace('href="/', 'href="' . static::URI, $content);
|
||||
$content = str_replace('src=\'/', 'src=\'' . static::URI, $content);
|
||||
$content = str_replace('href=\'/', 'href=\'' . static::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$item['title'] = trim($title->innertext);
|
||||
$item['uri'] = static::URI . $title->href;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
// Replace image 'src' with the one in 'data-src'
|
||||
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content);
|
||||
$content = preg_replace('@data-src=@', 'src=', $content);
|
||||
|
||||
// Remove date in the content to prevent content update while the video is getting older
|
||||
$content = preg_replace('@<div class="meta-sub light">.*<span>[^<]*</span>[^<]*</div>@', '', $content);
|
||||
|
||||
$item['content'] = $content;
|
||||
$item['title'] = trim($title->innertext);
|
||||
$item['uri'] = static::URI . substr($title->href, 1);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -61,8 +61,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
|
||||
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Amazon.');
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
foreach($html->find('li.s-result-item') as $element) {
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const MAINTAINER = 'captn3m0, sal0max';
|
||||
const NAME = 'Amazon Price Tracker';
|
||||
const URI = 'https://www.amazon.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
@@ -32,6 +32,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
'Mexico' => 'com.mx',
|
||||
'Netherlands' => 'nl',
|
||||
'Spain' => 'es',
|
||||
'Sweden' => 'se',
|
||||
'United Kingdom' => 'co.uk',
|
||||
'United States' => 'com',
|
||||
),
|
||||
@@ -39,6 +40,15 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
),
|
||||
));
|
||||
|
||||
const PRICE_SELECTORS = array(
|
||||
'#priceblock_ourprice',
|
||||
'.priceBlockBuyingPriceString',
|
||||
'#newBuyBoxPrice',
|
||||
'#tp_price_block_total_price_ww',
|
||||
'span.offer-price',
|
||||
'.a-color-price',
|
||||
);
|
||||
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
@@ -53,7 +63,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
*/
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('asin'))) {
|
||||
return $this->getDomainName() . '/dp/' . $this->getInput('asin') . '/';
|
||||
return $this->getDomainName() . '/dp/' . $this->getInput('asin');
|
||||
}
|
||||
return parent::getURI();
|
||||
}
|
||||
@@ -134,27 +144,43 @@ EOT;
|
||||
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
|
||||
// data-asin-currency-code="USD" data-substitute-count="-1" ... />
|
||||
if ($asinData) {
|
||||
return [
|
||||
return array(
|
||||
'price' => $asinData->getAttribute('data-asin-price'),
|
||||
'currency' => $asinData->getAttribute('data-asin-currency-code'),
|
||||
'shipping' => $asinData->getAttribute('data-asin-shipping')
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function scrapePriceGeneric($html) {
|
||||
$priceDiv = $html->find('span.offer-price', 0) ?: $html->find('.a-color-price', 0);
|
||||
$priceDiv = null;
|
||||
|
||||
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
|
||||
foreach(self::PRICE_SELECTORS as $sel) {
|
||||
$priceDiv = $html->find($sel, 0);
|
||||
if ($priceDiv) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($matches) === 3) {
|
||||
return [
|
||||
'price' => $matches[2],
|
||||
'currency' => $matches[1],
|
||||
if (!$priceDiv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$priceString = $priceDiv->plaintext;
|
||||
|
||||
preg_match('/[\d.,]+/', $priceString, $matches);
|
||||
|
||||
$price = $matches[0];
|
||||
$currency = trim(str_replace($price, '', $priceString), " \t\n\r\0\x0B\xC2\xA0");
|
||||
|
||||
if ($price != null && $currency != null) {
|
||||
return array(
|
||||
'price' => $price,
|
||||
'currency' => $currency,
|
||||
'shipping' => '0'
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -175,6 +201,8 @@ EOT;
|
||||
'title' => $this->title,
|
||||
'uri' => $this->getURI(),
|
||||
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
|
||||
// This is to ensure that feed readers notice the price change
|
||||
'uid' => md5($data['price'])
|
||||
);
|
||||
|
||||
if ($data['shipping'] !== '0') {
|
||||
|
@@ -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,9 +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)
|
||||
or returnServerError('Could not request Anidex: ' . $search_url);
|
||||
$html = getSimpleHTMLDOM($search_url, $headers, $opt);
|
||||
$links = $html->find('a');
|
||||
$results = array();
|
||||
foreach ($links as $link)
|
||||
@@ -156,10 +165,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 +201,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;
|
||||
|
@@ -39,8 +39,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
//Retrive page contents
|
||||
$url = self::URI . 'history-0-1/' . $requestFilter;
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request Anime-Ultime: ' . $url);
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
//Relases are sorted by day : process each day individually
|
||||
foreach($html->find('div.history', 0)->find('h3') as $daySection) {
|
||||
@@ -87,8 +86,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
if(!empty($item_uri)) {
|
||||
|
||||
// Retrieve description from description page
|
||||
$html_item = getContents($item_uri)
|
||||
or returnServerError('Could not request Anime-Ultime: ' . $item_uri);
|
||||
$html_item = getContents($item_uri);
|
||||
$item_description = substr(
|
||||
$html_item,
|
||||
strpos($html_item, 'class="principal_contain" align="center">') + 41
|
||||
@@ -102,7 +100,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();
|
||||
|
150
bridges/AppleAppStoreBridge.php
Normal file
150
bridges/AppleAppStoreBridge.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
class AppleAppStoreBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const NAME = 'Apple App Store';
|
||||
const URI = 'https://apps.apple.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns version updates for a specific application';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'id' => array(
|
||||
'name' => 'Application ID',
|
||||
'required' => true,
|
||||
'exampleValue' => '310633997'
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Platform',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'iPad' => 'ipad',
|
||||
'iPhone' => 'iphone',
|
||||
'Mac' => 'mac',
|
||||
|
||||
// The following 2 are present in responses
|
||||
// but not yet tested
|
||||
'Web' => 'web',
|
||||
'Apple TV' => 'appletv',
|
||||
),
|
||||
'defaultValue' => 'iphone',
|
||||
),
|
||||
'country' => array(
|
||||
'name' => 'Store Country',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'US' => 'US',
|
||||
'India' => 'IN',
|
||||
'Canada' => 'CA',
|
||||
'Germany' => 'DE',
|
||||
),
|
||||
'defaultValue' => 'US',
|
||||
),
|
||||
));
|
||||
|
||||
const PLATFORM_MAPPING = array(
|
||||
'iphone' => 'ios',
|
||||
'ipad' => 'ios',
|
||||
);
|
||||
|
||||
private function makeHtmlUrl($id, $country){
|
||||
return 'https://apps.apple.com/' . $country . '/app/id' . $id;
|
||||
}
|
||||
|
||||
private function makeJsonUrl($id, $platform, $country){
|
||||
return "https://amp-api.apps.apple.com/v1/catalog/$country/apps/$id?platform=$platform&extend=versionHistory";
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if (isset($this->name)) {
|
||||
return $this->name . ' - AppStore Updates';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* In case of some platforms, the data is present in the initial response
|
||||
*/
|
||||
private function getDataFromShoebox($id, $platform, $country){
|
||||
$uri = $this->makeHtmlUrl($id, $country);
|
||||
$html = getSimpleHTMLDOMCached($uri, 3600);
|
||||
$script = $html->find('script[id="shoebox-ember-data-store"]', 0);
|
||||
|
||||
$json = json_decode($script->innertext, true);
|
||||
return $json['data'];
|
||||
}
|
||||
|
||||
private function getJWTToken($id, $platform, $country){
|
||||
$uri = $this->makeHtmlUrl($id, $country);
|
||||
|
||||
$html = getSimpleHTMLDOMCached($uri, 3600);
|
||||
|
||||
$meta = $html->find('meta[name="web-experience-app/config/environment"]', 0);
|
||||
|
||||
$json = urldecode($meta->content);
|
||||
|
||||
$json = json_decode($json);
|
||||
|
||||
return $json->MEDIA_API->token;
|
||||
}
|
||||
|
||||
private function getAppData($id, $platform, $country, $token){
|
||||
$uri = $this->makeJsonUrl($id, $platform, $country);
|
||||
|
||||
$headers = array(
|
||||
"Authorization: Bearer $token",
|
||||
);
|
||||
|
||||
$json = json_decode(getContents($uri, $headers), true);
|
||||
|
||||
return $json['data'][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the version history from the data received
|
||||
* @return array list of versions with details on each element
|
||||
*/
|
||||
private function getVersionHistory($data, $platform){
|
||||
switch($platform) {
|
||||
case 'mac':
|
||||
return $data['relationships']['platforms']['data'][0]['attributes']['versionHistory'];
|
||||
default:
|
||||
$os = self::PLATFORM_MAPPING[$platform];
|
||||
return $data['attributes']['platformAttributes'][$os]['versionHistory'];
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$id = $this->getInput('id');
|
||||
$country = $this->getInput('country');
|
||||
$platform = $this->getInput('p');
|
||||
|
||||
switch ($platform) {
|
||||
case 'mac':
|
||||
$data = $this->getDataFromShoebox($id, $platform, $country);
|
||||
break;
|
||||
|
||||
default:
|
||||
$token = $this->getJWTToken($id, $platform, $country);
|
||||
$data = $this->getAppData($id, $platform, $country, $token);
|
||||
}
|
||||
|
||||
$versionHistory = $this->getVersionHistory($data, $platform);
|
||||
$name = $this->name = $data['attributes']['name'];
|
||||
$author = $data['attributes']['artistName'];
|
||||
|
||||
foreach ($versionHistory as $row) {
|
||||
$item = array();
|
||||
|
||||
$item['content'] = nl2br($row['releaseNotes']);
|
||||
$item['title'] = $name . ' - ' . $row['versionDisplay'];
|
||||
$item['timestamp'] = $row['releaseDate'];
|
||||
$item['author'] = $author;
|
||||
|
||||
$item['uri'] = $this->makeHtmlUrl($id, $country);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,59 +4,52 @@ class AppleMusicBridge extends BridgeAbstract {
|
||||
const NAME = 'Apple Music';
|
||||
const URI = 'https://www.apple.com';
|
||||
const DESCRIPTION = 'Fetches the latest releases from an artist';
|
||||
const MAINTAINER = 'Limero';
|
||||
const PARAMETERS = [[
|
||||
'url' => [
|
||||
'name' => 'Artist URL',
|
||||
'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274',
|
||||
const MAINTAINER = 'bockiii';
|
||||
const PARAMETERS = array(array(
|
||||
'artist' => array(
|
||||
'name' => 'Artist ID',
|
||||
'exampleValue' => '909253',
|
||||
'required' => true,
|
||||
],
|
||||
'imgSize' => [
|
||||
'name' => 'Image size for thumbnails (in px)',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 512,
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Latest X Releases (max 50)',
|
||||
'defaultValue' => '10',
|
||||
'required' => true,
|
||||
]
|
||||
]];
|
||||
),
|
||||
));
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
# Limit the amount of releases to 50
|
||||
if ($this->getInput('limit') > 50) {
|
||||
$limit = 50;
|
||||
} else {
|
||||
$limit = $this->getInput('limit');
|
||||
}
|
||||
|
||||
$imgSize = $this->getInput('imgSize');
|
||||
$url = 'https://itunes.apple.com/lookup?id='
|
||||
. $this->getInput('artist')
|
||||
. '&entity=album&limit='
|
||||
. $limit .
|
||||
'&sort=recent';
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
// Grab the json data from the page
|
||||
$html = $html->find('script[id=shoebox-ember-data-store]', 0);
|
||||
$html = strstr($html, '{');
|
||||
$html = substr($html, 0, -9);
|
||||
$json = json_decode($html);
|
||||
|
||||
// Loop through each object
|
||||
foreach ($json->included as $obj) {
|
||||
if ($obj->type === 'lockup/album') {
|
||||
$this->items[] = [
|
||||
'title' => $obj->attributes->artistName . ' - ' . $obj->attributes->name,
|
||||
'uri' => $obj->attributes->url,
|
||||
'timestamp' => $obj->attributes->releaseDate,
|
||||
'enclosures' => $obj->relationships->artwork->data->id,
|
||||
];
|
||||
} elseif ($obj->type === 'image') {
|
||||
$images[$obj->id] = $obj->attributes->url;
|
||||
foreach ($json->results as $obj) {
|
||||
if ($obj->wrapperType === 'collection') {
|
||||
$this->items[] = array(
|
||||
'title' => $obj->artistName . ' - ' . $obj->collectionName,
|
||||
'uri' => $obj->collectionViewUrl,
|
||||
'timestamp' => $obj->releaseDate,
|
||||
'enclosures' => $obj->artworkUrl100,
|
||||
'content' => '<a href=' . $obj->collectionViewUrl
|
||||
. '><img src="' . $obj->artworkUrl100 . '" /></a><br><br>'
|
||||
. $obj->artistName . ' - ' . $obj->collectionName
|
||||
. '<br>'
|
||||
. $obj->copyright,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Add the images to each item
|
||||
foreach ($this->items as &$item) {
|
||||
$item['enclosures'] = [
|
||||
str_replace('{w}x{h}bb.{f}', $imgSize . 'x0w.jpg', $images[$item['enclosures']]),
|
||||
];
|
||||
}
|
||||
|
||||
// Sort the order to put the latest albums first
|
||||
usort($this->items, function($a, $b){
|
||||
return $a['timestamp'] < $b['timestamp'];
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -39,15 +39,13 @@ class ArtStationBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
$jsonSearchURL = self::URI . '/api/v2/search/projects.json';
|
||||
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts)
|
||||
or returnServerError('Could not fetch JSON for search query.');
|
||||
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts);
|
||||
return json_decode($jsonSearchStr);
|
||||
}
|
||||
|
||||
private function fetchProject($hashID) {
|
||||
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
|
||||
$jsonProjectStr = getContents($jsonProjectURL)
|
||||
or returnServerError('Could not fetch JSON for project.');
|
||||
$jsonProjectStr = getContents($jsonProjectURL);
|
||||
return json_decode($jsonProjectStr);
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class Arte7Bridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
// const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Arte +7';
|
||||
const URI = 'https://www.arte.tv/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
@@ -91,8 +91,7 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
);
|
||||
|
||||
$input = getContents($url, $header)
|
||||
or returnServerError('Could not request ARTE.');
|
||||
$input = getContents($url, $header);
|
||||
$input_json = json_decode($input, true);
|
||||
|
||||
foreach($input_json['videos'] as $element) {
|
||||
|
@@ -36,8 +36,7 @@ class AsahiShimbunAJWBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')))
|
||||
or returnServerError('Could not load content');
|
||||
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')));
|
||||
|
||||
foreach($html->find('#MainInner li a') as $element) {
|
||||
if ($element->parent()->class == 'HeadlineTopImage-S') {
|
||||
|
@@ -16,8 +16,7 @@ class AskfmBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
|
@@ -2,8 +2,8 @@
|
||||
class AtmoNouvelleAquitaineBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Atmo Nouvelle Aquitaine';
|
||||
const URI = 'https://www.atmo-nouvelleaquitaine.org/monair/commune/';
|
||||
const DESCRIPTION = 'Fetches the latest air polution of Bordeaux from Atmo Nouvelle Aquitaine';
|
||||
const URI = 'https://www.atmo-nouvelleaquitaine.org';
|
||||
const DESCRIPTION = 'Fetches the latest air polution of cities in Nouvelle Aquitaine from Atmo';
|
||||
const MAINTAINER = 'floviolleau';
|
||||
const PARAMETERS = array(array(
|
||||
'cities' => array(
|
||||
@@ -27,10 +27,9 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = self::URI . $this->getInput('cities');
|
||||
$uri = self::URI . '/monair/commune/' . $this->getInput('cities');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request ' . $uri);
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
$this->dom = $html->find('#block-system-main .city-prevision-map', 0);
|
||||
|
||||
@@ -77,7 +76,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract {
|
||||
|
||||
private function getLegendIndexes() {
|
||||
$rawIndexes = $this->dom->find('.prevision-legend .prevision-legend-label');
|
||||
$indexes = [];
|
||||
$indexes = array();
|
||||
for ($i = 0; $i < count($rawIndexes); $i++) {
|
||||
if ($rawIndexes[$i]->hasAttribute('data-color')) {
|
||||
$indexes[$rawIndexes[$i]->getAttribute('data-color')] = $rawIndexes[$i]->innertext;
|
||||
|
57
bridges/AtmoOccitanieBridge.php
Normal file
57
bridges/AtmoOccitanieBridge.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class AtmoOccitanieBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Atmo Occitanie';
|
||||
const URI = 'https://www.atmo-occitanie.org/';
|
||||
const DESCRIPTION = 'Fetches the latest air polution of cities in Occitanie from Atmo';
|
||||
const MAINTAINER = 'floviolleau';
|
||||
const PARAMETERS = array(array(
|
||||
'city' => array(
|
||||
'name' => 'Ville',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 7200;
|
||||
|
||||
public function collectData() {
|
||||
$uri = self::URI . $this->getInput('city');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
$generalMessage = $html->find('.landing-ville .city-banner .iqa-avertissement', 0)->innertext;
|
||||
$recommendationsDom = $html->find('.landing-ville .recommandations', 0);
|
||||
$recommendationsItemDom = $recommendationsDom->find('.recommandation-item .label');
|
||||
|
||||
$recommendationsMessage = '';
|
||||
|
||||
$i = 0;
|
||||
$len = count($recommendationsItemDom);
|
||||
foreach ($recommendationsItemDom as $key => $value) {
|
||||
if ($i == 0) {
|
||||
$recommendationsMessage .= trim($value->innertext) . '.';
|
||||
} else {
|
||||
$recommendationsMessage .= ' ' . trim($value->innertext) . '.';
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
$lastRecommendationsDom = $recommendationsDom->find('.col-md-6', -1);
|
||||
$informationHeaderMessage = $lastRecommendationsDom->find('.heading', 0)->innertext;
|
||||
$indice = $lastRecommendationsDom->find('.current-indice .indice div', 0)->innertext;
|
||||
$informationDescriptionMessage = $lastRecommendationsDom->find('.current-indice .description p', 0)->innertext;
|
||||
|
||||
$message = "$generalMessage L'indice est de $indice/10. $informationDescriptionMessage. $recommendationsMessage";
|
||||
$city = $this->getInput('city');
|
||||
|
||||
$item['uri'] = $uri;
|
||||
$today = date('d/m/Y');
|
||||
$item['title'] = "Bulletin de l'air du $today pour la ville : $city.";
|
||||
//$item['title'] .= ' Retrouvez plus d\'informations en allant sur atmo-occitanie.org #QualiteAir. ' . $message;
|
||||
$item['title'] .= ' #QualiteAir. ' . $message;
|
||||
$item['author'] = 'floviolleau';
|
||||
$item['content'] = $message;
|
||||
$item['uid'] = hash('sha256', $item['title']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
@@ -7,51 +7,14 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array(
|
||||
'Afficher les offres de véhicules disponible sur la recheche AutoJM' => array(
|
||||
'url' => array(
|
||||
'name' => 'URL du modèle',
|
||||
'name' => 'URL de la page de recherche',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
|
||||
'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p'
|
||||
'exampleValue' => 'recherche?brands[]=peugeot&ranges[]=peugeot-nouvelle-308-2021-5p'
|
||||
),
|
||||
'energy' => array(
|
||||
'name' => 'Carburant',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'-' => '',
|
||||
'Diesel' => 1,
|
||||
'Essence' => 3,
|
||||
'Hybride' => 5
|
||||
),
|
||||
'title' => 'Carburant'
|
||||
),
|
||||
'transmission' => array(
|
||||
'name' => 'Transmission',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'-' => '',
|
||||
'Automatique' => 1,
|
||||
'Manuelle' => 2
|
||||
),
|
||||
'title' => 'Transmission'
|
||||
),
|
||||
'priceMin' => array(
|
||||
'name' => 'Prix minimum',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Prix minimum du véhicule',
|
||||
'exampleValue' => '10000',
|
||||
'defaultValue' => '0'
|
||||
),
|
||||
'priceMax' => array(
|
||||
'name' => 'Prix maximum',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Prix maximum du véhicule',
|
||||
'exampleValue' => '15000',
|
||||
'defaultValue' => '150000'
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
@@ -62,10 +25,8 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM':
|
||||
$html = getSimpleHTMLDOMCached(self::URI . $this->getInput('url'), 86400);
|
||||
$name = html_entity_decode($html->find('title', 0)->plaintext);
|
||||
return $name;
|
||||
case 'Afficher les offres de véhicules disponible sur la recheche AutoJM':
|
||||
return 'AutoJM | Recherche de véhicules';
|
||||
break;
|
||||
default:
|
||||
return parent::getName();
|
||||
@@ -75,112 +36,100 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$model_url = self::URI . $this->getInput('url');
|
||||
// Get the number of result for this search
|
||||
$search_url = self::URI . $this->getInput('url') . '&open=energy&onlyFilters=false';
|
||||
|
||||
// Get the session cookies and the form token
|
||||
$this->getInitialParameters($model_url);
|
||||
|
||||
// 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)
|
||||
or returnServerError('Could not request AutoJM.');
|
||||
$json = getContents($search_url, $header);
|
||||
|
||||
// Extract the HTML content from the JSON result
|
||||
$data = json_decode($json);
|
||||
$html = str_get_html($data->content);
|
||||
|
||||
// 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);
|
||||
$nb_results = $data->nbResults;
|
||||
$total_pages = ceil($nb_results / 15);
|
||||
|
||||
// Limit the number of page to analyse to 10
|
||||
for($page = 1; $page <= $total_pages && $page <= 10; $page++) {
|
||||
// Get the result the next page
|
||||
$html = $this->getResults($page);
|
||||
|
||||
// Go through every car of the search
|
||||
$list = $html->find('div[class*=card-car card-car--listing]');
|
||||
foreach ($list as $car) {
|
||||
|
||||
// Get the info about the car offer
|
||||
$image = $car->find('div[class=card-car__header__img]', 0)->find('img', 0)->src;
|
||||
// Decode HTML attribute JSON data
|
||||
$car_data = json_decode(html_entity_decode($car->{'data-layer'}));
|
||||
$car_model = $car->{'data-title'} . ' ' . $car->{'data-suptitle'};
|
||||
$availability = $car->find('div[class=card-car__modalites]', 0)->find('div[class=col]', 0)->plaintext;
|
||||
$warranty = $car->find('div[data-type=WarrantyCard]', 0)->plaintext;
|
||||
$discount_html = $car->find('div[class=subtext vehicle_reference_element]', 0);
|
||||
// Check if there is any discount info displayed
|
||||
if ($discount_html != null) {
|
||||
$discount = $discount_html->plaintext;
|
||||
$reference_price_value = $discount_html->find('span[data-cfg=vehicle__reference_price]', 0)->plaintext;
|
||||
$discount_percent_value = $discount_html->find('span[data-cfg=vehicle__discount_percent]', 0)->plaintext;
|
||||
$reference_price = '<li>Prix de référence : <s>' . $reference_price_value . '</s></li>';
|
||||
$discount_percent = '<li>Réduction : ' . $discount_percent_value . ' %</li>';
|
||||
} else {
|
||||
$discount = 'inconnue';
|
||||
$reference_price = '';
|
||||
$discount_percent = '';
|
||||
}
|
||||
$price = $element->find('span[class=price red h1]', 0)->plaintext;
|
||||
$price = $car_data->price;
|
||||
$kilometer = $car->find('span[data-cfg=vehicle__kilometer]', 0)->plaintext;
|
||||
$energy = $car->find('span[data-cfg=vehicle__energy__label]', 0)->plaintext;
|
||||
$power = $car->find('span[data-cfg=vehicle__tax_horse_power]', 0)->plaintext;
|
||||
$seats = $car->find('span[data-cfg=vehicle__seats]', 0)->plaintext;
|
||||
$doors = $car->find('span[data-cfg=vehicle__door__label]', 0)->plaintext;
|
||||
$transmission = $car->find('span[data-cfg=vehicle__transmission]', 0)->plaintext;
|
||||
$loa_html = $car->find('span[data-cfg=vehicle__loa]', 0);
|
||||
// Check if any LOA price is displayed
|
||||
if($loa_html != null) {
|
||||
$loa_value = $car->find('span[data-cfg=vehicle__loa]', 0)->plaintext;
|
||||
$loa = '<li>LOA : à partir de ' . $loa_value . ' / mois </li>';
|
||||
} else {
|
||||
$loa = '';
|
||||
}
|
||||
|
||||
// Construct the new item
|
||||
$item = array();
|
||||
$item['title'] = $finish_name . ' ' . $serie;
|
||||
$item['title'] = $car_model;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
|
||||
. $finish_name . ' ' . $serie . '</p>';
|
||||
. $car_model . '</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']);
|
||||
|
||||
$item['content'] .= '<li>Prix : ' . $price . ' €</li>';
|
||||
$item['content'] .= $reference_price;
|
||||
$item['content'] .= $loa;
|
||||
$item['content'] .= $discount_percent;
|
||||
$item['content'] .= '<li>Garantie : ' . $warranty . '</li>';
|
||||
$item['content'] .= '<li>Kilométrage : ' . $kilometer . ' km</li>';
|
||||
$item['content'] .= '<li>Energie : ' . $energy . '</li>';
|
||||
$item['content'] .= '<li>Puissance: ' . $power . ' CV Fiscaux</li>';
|
||||
$item['content'] .= '<li>Nombre de Places : ' . $seats . ' place(s)</li>';
|
||||
$item['content'] .= '<li>Nombre de portes : ' . $doors . '</li>';
|
||||
$item['content'] .= '<li>Boite de vitesse : ' . $transmission . '</li></ul>';
|
||||
$item['uri'] = $car_data->{'uri'};
|
||||
$item['uid'] = hash('md5', $item['content']);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
private function getResults(int $page)
|
||||
{
|
||||
$user_input = $this->getInput('url');
|
||||
$search_data = preg_replace('#(recherche|recherche/[0-9]{1,10})\?#', 'recherche/' . $page . '?', $user_input);
|
||||
|
||||
// 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);
|
||||
$search_url = self::URI . $search_data . '&open=energy&onlyFilters=false';
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
$this->cookies = trim(substr($cookies, 1));
|
||||
// Get the HTML content of the page
|
||||
$html = getSimpleHTMLDOMCached($search_url);
|
||||
|
||||
// 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;
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
54
bridges/AwwwardsBridge.php
Normal file
54
bridges/AwwwardsBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -220,8 +220,7 @@ class BadDragonBridge extends BridgeAbstract {
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Sales':
|
||||
$sales = json_decode(getContents(self::URI . 'api/sales'))
|
||||
or returnServerError('Failed to query BD API');
|
||||
$sales = json_decode(getContents(self::URI . 'api/sales'));
|
||||
|
||||
foreach($sales as $sale) {
|
||||
$item = array();
|
||||
@@ -274,12 +273,10 @@ class BadDragonBridge extends BridgeAbstract {
|
||||
}
|
||||
break;
|
||||
case 'Clearance':
|
||||
$toyData = json_decode(getContents($this->inputToURL(true)))
|
||||
or returnServerError('Failed to query BD API');
|
||||
$toyData = json_decode(getContents($this->inputToURL(true)));
|
||||
|
||||
$productList = json_decode(getContents(self::URI
|
||||
. 'api/inventory-toy/product-list'))
|
||||
or returnServerError('Failed to query BD API');
|
||||
. 'api/inventory-toy/product-list'));
|
||||
|
||||
foreach($toyData->toys as $toy) {
|
||||
$item = array();
|
||||
|
@@ -3,23 +3,80 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
|
||||
const NAME = 'Baka Updates Manga Releases';
|
||||
const URI = 'https://www.mangaupdates.com/';
|
||||
const DESCRIPTION = 'Get the latest series releases';
|
||||
const MAINTAINER = 'fulmeek';
|
||||
const PARAMETERS = array(array(
|
||||
'series_id' => array(
|
||||
'name' => 'Series ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '12345'
|
||||
const MAINTAINER = 'fulmeek, KamaleiZestri';
|
||||
const PARAMETERS = array(
|
||||
'By series' => array(
|
||||
'series_id' => array(
|
||||
'name' => 'Series ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '12345'
|
||||
)
|
||||
),
|
||||
'By list' => array(
|
||||
'list_id' => array(
|
||||
'name' => 'List ID and Type',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '123456&list=read'
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
const LIMIT_COLS = 5;
|
||||
const LIMIT_ITEMS = 10;
|
||||
const RELEASES_URL = 'https://www.mangaupdates.com/releases.html';
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Series not found');
|
||||
if($this -> queriedContext == 'By series')
|
||||
$this -> collectDataBySeries();
|
||||
else //queriedContext == 'By list'
|
||||
$this -> collectDataByList();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if($this -> queriedContext == 'By series') {
|
||||
$series_id = $this->getInput('series_id');
|
||||
if (!empty($series_id)) {
|
||||
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
|
||||
}
|
||||
} else //queriedContext == 'By list'
|
||||
return self::RELEASES_URL;
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!empty($this->feedName)) {
|
||||
return $this->feedName . ' - ' . self::NAME;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getSanitizedHash($string) {
|
||||
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
|
||||
}
|
||||
|
||||
private function filterText($text) {
|
||||
return rtrim($text, '* ');
|
||||
}
|
||||
|
||||
private function filterHTML($text) {
|
||||
return $this->filterText(html_entity_decode($text));
|
||||
}
|
||||
|
||||
private function findID($manga) {
|
||||
// sometimes new series are on the release list that have no ID. just drop them.
|
||||
if(@$this -> filterHTML($manga -> find('a', 0) -> href) != null) {
|
||||
preg_match('/id=([0-9]*)/', $this -> filterHTML($manga -> find('a', 0) -> href), $match);
|
||||
return $match[1];
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function collectDataBySeries() {
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
// content is an unstructured pile of divs, ugly to parse
|
||||
$cols = $html->find('div#main_content div.row > div.text');
|
||||
@@ -68,36 +125,62 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
|
||||
|
||||
$item['title'] = implode(' ', $title);
|
||||
$item['uri'] = $this->getURI();
|
||||
$item['uid'] = $this->getSanitizedHash($item['title']);
|
||||
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
$series_id = $this->getInput('series_id');
|
||||
if (!empty($series_id)) {
|
||||
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
|
||||
private function collectDataByList() {
|
||||
$this -> feedName = 'Releases';
|
||||
$list = array();
|
||||
|
||||
$releasesHTML = getSimpleHTMLDOM(self::RELEASES_URL);
|
||||
|
||||
$list_id = $this -> getInput('list_id');
|
||||
$listHTML = getSimpleHTMLDOM('https://www.mangaupdates.com/mylist.html?id=' . $list_id);
|
||||
|
||||
//get ids of the manga that the user follows,
|
||||
$parts = $listHTML -> find('table#ptable tr > td.pl');
|
||||
foreach($parts as $part) {
|
||||
$list[] = $this -> findID($part);
|
||||
}
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!empty($this->feedName)) {
|
||||
return $this->feedName . ' - ' . self::NAME;
|
||||
//similar to above, but the divs are in groups of 3.
|
||||
$cols = $releasesHTML -> find('div#main_content div.row > div.pbreak');
|
||||
$rows = array_slice(array_chunk($cols, 3), 0);
|
||||
|
||||
foreach($rows as $cols) {
|
||||
//check if current manga is in user's list.
|
||||
$id = $this -> findId($cols[0]);
|
||||
if(!array_search($id, $list)) continue;
|
||||
|
||||
$item = array();
|
||||
$title = array();
|
||||
|
||||
$item['content'] = '';
|
||||
|
||||
$objTitle = $cols[0];
|
||||
if ($objTitle) {
|
||||
$title[] = $this->filterHTML($objTitle->plaintext);
|
||||
$item['content'] .= '<p>Series: ' . $this->filterHTML($objTitle -> innertext) . '</p>';
|
||||
}
|
||||
|
||||
$objVolChap = $cols[1];
|
||||
if ($objVolChap && !empty($objVolChap->plaintext))
|
||||
$title[] = $this -> filterHTML($objVolChap -> innertext);
|
||||
|
||||
$objAuthor = $cols[2];
|
||||
if ($objAuthor && !empty($objAuthor->plaintext)) {
|
||||
$item['author'] = $this->filterHTML($objAuthor -> plaintext);
|
||||
$item['content'] .= '<p>Groups: ' . $this->filterHTML($objAuthor -> innertext) . '</p>';
|
||||
}
|
||||
|
||||
$item['title'] = implode(' ', $title);
|
||||
$item['uri'] = self::URI . 'releases.html?search=' . $id . '&stype=series';
|
||||
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getSanitizedHash($string) {
|
||||
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
|
||||
}
|
||||
|
||||
private function filterText($text) {
|
||||
return rtrim($text, '* ');
|
||||
}
|
||||
|
||||
private function filterHTML($text) {
|
||||
return $this->filterText(html_entity_decode($text));
|
||||
}
|
||||
}
|
||||
|
@@ -1,73 +1,289 @@
|
||||
<?php
|
||||
class BandcampBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'sebsauvage';
|
||||
const NAME = 'Bandcamp Tag';
|
||||
const MAINTAINER = 'sebsauvage, Roliga';
|
||||
const NAME = 'Bandcamp Bridge';
|
||||
const URI = 'https://bandcamp.com/';
|
||||
const CACHE_TIMEOUT = 600; // 10min
|
||||
const DESCRIPTION = 'New bandcamp release by tag';
|
||||
const PARAMETERS = array( array(
|
||||
'tag' => array(
|
||||
'name' => 'tag',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
const DESCRIPTION = 'New bandcamp releases by tag, band or album';
|
||||
const PARAMETERS = array(
|
||||
'By tag' => array(
|
||||
'tag' => array(
|
||||
'name' => 'tag',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'By band' => array(
|
||||
'band' => array(
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the band page URL',
|
||||
'required' => true
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
),
|
||||
'defaultValue' => 'changes'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Number of releases to return',
|
||||
'defaultValue' => 5
|
||||
)
|
||||
),
|
||||
'By label' => array(
|
||||
'label' => array(
|
||||
'name' => 'label',
|
||||
'type' => 'text',
|
||||
'title' => 'label name as seen in the label page URL',
|
||||
'required' => true
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
),
|
||||
'defaultValue' => 'changes'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Number of releases to return',
|
||||
'defaultValue' => 5
|
||||
)
|
||||
),
|
||||
'By album' => array(
|
||||
'band' => array(
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the album page URL',
|
||||
'required' => true
|
||||
),
|
||||
'album' => array(
|
||||
'name' => 'album',
|
||||
'type' => 'text',
|
||||
'title' => 'Album name as seen in the album page URL',
|
||||
'required' => true
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
),
|
||||
'defaultValue' => 'tracks'
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
const IMGURI = 'https://f4.bcbits.com/';
|
||||
const IMGSIZE_300PX = 23;
|
||||
const IMGSIZE_700PX = 16;
|
||||
|
||||
private $feedName;
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s4.bcbits.com/img/bc_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI . 'api/hub/1/dig_deeper';
|
||||
$data = $this->buildRequestJson();
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data)
|
||||
);
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
);
|
||||
$content = getContents($url, $header, $opts)
|
||||
or returnServerError('Could not complete request to: ' . $url);
|
||||
|
||||
$json = json_decode($content);
|
||||
|
||||
if ($json->ok !== true) {
|
||||
returnServerError('Invalid response');
|
||||
}
|
||||
|
||||
foreach ($json->items as $entry) {
|
||||
$url = $entry->tralbum_url;
|
||||
$artist = $entry->artist;
|
||||
$title = $entry->title;
|
||||
// e.g. record label is the releaser, but not the artist
|
||||
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
|
||||
|
||||
$full_title = $artist . ' - ' . $title;
|
||||
$full_artist = $artist;
|
||||
if (isset($releaser)) {
|
||||
$full_title .= ' (' . $releaser . ')';
|
||||
$full_artist .= ' (' . $releaser . ')';
|
||||
}
|
||||
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
|
||||
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
|
||||
|
||||
$item = array(
|
||||
'uri' => $url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
$url = self::URI . 'api/hub/1/dig_deeper';
|
||||
$data = $this->buildRequestJson();
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data)
|
||||
);
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title";
|
||||
$item['enclosures'] = array($img);
|
||||
$this->items[] = $item;
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
);
|
||||
$content = getContents($url, $header, $opts);
|
||||
|
||||
$json = json_decode($content);
|
||||
|
||||
if ($json->ok !== true) {
|
||||
returnServerError('Invalid response');
|
||||
}
|
||||
|
||||
foreach ($json->items as $entry) {
|
||||
$url = $entry->tralbum_url;
|
||||
$artist = $entry->artist;
|
||||
$title = $entry->title;
|
||||
// e.g. record label is the releaser, but not the artist
|
||||
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
|
||||
|
||||
$full_title = $artist . ' - ' . $title;
|
||||
$full_artist = $artist;
|
||||
if (isset($releaser)) {
|
||||
$full_title .= ' (' . $releaser . ')';
|
||||
$full_artist .= ' (' . $releaser . ')';
|
||||
}
|
||||
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
|
||||
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
|
||||
|
||||
$item = array(
|
||||
'uri' => $url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title
|
||||
);
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title";
|
||||
$item['enclosures'] = array($img);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
case 'By band':
|
||||
case 'By label':
|
||||
case 'By album':
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
|
||||
|
||||
if ($html->find('meta[name=title]', 0)) {
|
||||
$this->feedName = $html->find('meta[name=title]', 0)->content;
|
||||
} else {
|
||||
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
|
||||
}
|
||||
|
||||
$regex = '/band_id=(\d+)/';
|
||||
if(preg_match($regex, $html, $matches) == false)
|
||||
returnServerError('Unable to find band ID on: ' . $this->getURI());
|
||||
$band_id = $matches[1];
|
||||
|
||||
$tralbums = array();
|
||||
switch($this->queriedContext) {
|
||||
case 'By band':
|
||||
case 'By label':
|
||||
$query_data = array(
|
||||
'band_id' => $band_id
|
||||
);
|
||||
$band_data = $this->apiGet('mobile/22/band_details', $query_data);
|
||||
|
||||
$num_albums = min(count($band_data->discography), $this->getInput('limit'));
|
||||
for($i = 0; $i < $num_albums; $i++) {
|
||||
$album_basic_data = $band_data->discography[$i];
|
||||
|
||||
// 'a' or 't' for albums and individual tracks respectively
|
||||
$tralbum_type = substr($album_basic_data->item_type, 0, 1);
|
||||
|
||||
$query_data = array(
|
||||
'band_id' => $band_id,
|
||||
'tralbum_type' => $tralbum_type,
|
||||
'tralbum_id' => $album_basic_data->item_id
|
||||
);
|
||||
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
|
||||
}
|
||||
break;
|
||||
case 'By album':
|
||||
$regex = '/album=(\d+)/';
|
||||
if(preg_match($regex, $html, $matches) == false)
|
||||
returnServerError('Unable to find album ID on: ' . $this->getURI());
|
||||
$album_id = $matches[1];
|
||||
|
||||
$query_data = array(
|
||||
'band_id' => $band_id,
|
||||
'tralbum_type' => 'a',
|
||||
'tralbum_id' => $album_id
|
||||
);
|
||||
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($tralbums as $tralbum_data) {
|
||||
if ($tralbum_data->type === 'a' && $this->getInput('type') === 'tracks') {
|
||||
foreach ($tralbum_data->tracks as $track) {
|
||||
$query_data = array(
|
||||
'band_id' => $band_id,
|
||||
'tralbum_type' => 't',
|
||||
'tralbum_id' => $track->track_id
|
||||
);
|
||||
$track_data = $this->apiGet('mobile/22/tralbum_details', $query_data);
|
||||
|
||||
$this->items[] = $this->buildTralbumItem($track_data);
|
||||
}
|
||||
} else {
|
||||
$this->items[] = $this->buildTralbumItem($tralbum_data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function buildTralbumItem($tralbum_data){
|
||||
$band_data = $tralbum_data->band;
|
||||
|
||||
// Format title like: ARTIST - ALBUM/TRACK (OPTIONAL RELEASER)
|
||||
// Format artist/author like: ARTIST (OPTIONAL RELEASER)
|
||||
//
|
||||
// If the album/track is released under a label/a band other than the artist
|
||||
// themselves, append that releaser name to the title and artist/author.
|
||||
//
|
||||
// This sadly doesn't always work right for individual tracks as the artist
|
||||
// of the track is always set to the releaser.
|
||||
$artist = $tralbum_data->tralbum_artist;
|
||||
$full_title = $artist . ' - ' . $tralbum_data->title;
|
||||
$full_artist = $artist;
|
||||
if (isset($tralbum_data->label)) {
|
||||
$full_title .= ' (' . $tralbum_data->label . ')';
|
||||
$full_artist .= ' (' . $tralbum_data->label . ')';
|
||||
} elseif ($band_data->name !== $artist) {
|
||||
$full_title .= ' (' . $band_data->name . ')';
|
||||
$full_artist .= ' (' . $band_data->name . ')';
|
||||
}
|
||||
|
||||
$small_img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_300PX);
|
||||
$img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_700PX);
|
||||
|
||||
$item = array(
|
||||
'uri' => $tralbum_data->bandcamp_url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title,
|
||||
'enclosures' => array($img),
|
||||
'timestamp' => $tralbum_data->release_date
|
||||
);
|
||||
|
||||
$item['categories'] = array();
|
||||
foreach ($tralbum_data->tags as $tag) {
|
||||
$item['categories'][] = $tag->norm_name;
|
||||
}
|
||||
|
||||
// Give articles a unique UID depending on its track list
|
||||
// Releases should then show up as new articles when tracks are added
|
||||
if ($this->getInput('type') === 'changes') {
|
||||
$item['uid'] = "bandcamp/$band_data->band_id/$tralbum_data->id/";
|
||||
foreach ($tralbum_data->tracks as $track) {
|
||||
$item['uid'] .= $track->track_id;
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title<br/>";
|
||||
if ($tralbum_data->type === 'a') {
|
||||
$item['content'] .= '<ol>';
|
||||
foreach ($tralbum_data->tracks as $track) {
|
||||
$item['content'] .= "<li>$track->title</li>";
|
||||
}
|
||||
$item['content'] .= '</ol>';
|
||||
}
|
||||
if (!empty($tralbum_data->about)) {
|
||||
$item['content'] .= '<p>'
|
||||
. nl2br($tralbum_data->about)
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function buildRequestJson(){
|
||||
$requestJson = array(
|
||||
'tag' => $this->getInput('tag'),
|
||||
@@ -81,11 +297,107 @@ class BandcampBridge extends BridgeAbstract {
|
||||
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
|
||||
}
|
||||
|
||||
private function apiGet($endpoint, $query_data) {
|
||||
$url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data);
|
||||
$data = json_decode(getContents($url));
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return self::URI
|
||||
. 'tag/'
|
||||
. urlencode($this->getInput('tag'))
|
||||
. '?sort_field=date';
|
||||
}
|
||||
break;
|
||||
case 'By label':
|
||||
if(!is_null($this->getInput('label'))) {
|
||||
return 'https://'
|
||||
. $this->getInput('label')
|
||||
. '.bandcamp.com/music';
|
||||
}
|
||||
break;
|
||||
case 'By band':
|
||||
if(!is_null($this->getInput('band'))) {
|
||||
return 'https://'
|
||||
. $this->getInput('band')
|
||||
. '.bandcamp.com/music';
|
||||
}
|
||||
break;
|
||||
case 'By album':
|
||||
if(!is_null($this->getInput('band')) && !is_null($this->getInput('album'))) {
|
||||
return 'https://'
|
||||
. $this->getInput('band')
|
||||
. '.bandcamp.com/album/'
|
||||
. $this->getInput('album');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return $this->getInput('tag') . ' - Bandcamp Tag';
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return $this->getInput('tag') . ' - Bandcamp Tag';
|
||||
}
|
||||
break;
|
||||
case 'By band':
|
||||
if(isset($this->feedName)) {
|
||||
return $this->feedName . ' - Bandcamp Band';
|
||||
} elseif(!is_null($this->getInput('band'))) {
|
||||
return $this->getInput('band') . ' - Bandcamp Band';
|
||||
}
|
||||
break;
|
||||
case 'By label':
|
||||
if(isset($this->feedName)) {
|
||||
return $this->feedName . ' - Bandcamp Label';
|
||||
} elseif(!is_null($this->getInput('label'))) {
|
||||
return $this->getInput('label') . ' - Bandcamp Label';
|
||||
}
|
||||
break;
|
||||
case 'By album':
|
||||
if(isset($this->feedName)) {
|
||||
return $this->feedName . ' - Bandcamp Album';
|
||||
} elseif(!is_null($this->getInput('album'))) {
|
||||
return $this->getInput('album') . ' - Bandcamp Album';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// By tag
|
||||
$regex = '/^(https?:\/\/)?bandcamp\.com\/tag\/([^\/.&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['tag'] = urldecode($matches[2]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By band
|
||||
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['band'] = urldecode($matches[2]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By album
|
||||
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['band'] = urldecode($matches[2]);
|
||||
$params['album'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -3,19 +3,12 @@ class BastaBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'qwertygc';
|
||||
const NAME = 'Bastamag Bridge';
|
||||
const URI = 'http://www.bastamag.net/';
|
||||
const URI = 'https://www.bastamag.net/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData(){
|
||||
// Replaces all relative image URLs by absolute URLs.
|
||||
// Relative URLs always start with 'local/'!
|
||||
function replaceImageUrl($content){
|
||||
return preg_replace('/src=["\']{1}([^"\']+)/ims', 'src=\'' . self::URI . '$1\'', $content);
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend')
|
||||
or returnServerError('Could not request Bastamag.');
|
||||
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend');
|
||||
|
||||
$limit = 0;
|
||||
|
||||
@@ -25,7 +18,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);
|
||||
$item['content'] = replaceImageUrl(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++;
|
||||
}
|
||||
|
@@ -35,28 +35,23 @@ class BinanceBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
protected function collectBlogData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not fetch Binance blog data.');
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
foreach($html->find('div[direction="row"]') as $element) {
|
||||
$appData = $html->find('script[id="__APP_DATA"]');
|
||||
$appDataJson = json_decode($appData[0]->innertext);
|
||||
|
||||
$date = $element->find('div[direction="column"]', 0);
|
||||
$day = $date->find('div', 0)->innertext;
|
||||
$month = $date->find('div', 1)->innertext;
|
||||
$extractedDate = $day . ' ' . $month;
|
||||
foreach($appDataJson->pageData->redux->blogList->blogList as $element) {
|
||||
|
||||
$abstract = $element->find('div[direction="column"]', 1);
|
||||
$a = $abstract->find('a', 0);
|
||||
$uri = self::URI . $a->href;
|
||||
$title = $a->innertext;
|
||||
|
||||
$full = getSimpleHTMLDOMCached($uri);
|
||||
$content = $full->find('div.desc', 1);
|
||||
$date = $element->postTime;
|
||||
$abstract = $element->brief;
|
||||
$uri = self::URI . '/' . $element->lang . '/blog/' . $element->idStr;
|
||||
$title = $element->title;
|
||||
$content = $element->content;
|
||||
|
||||
$item = array();
|
||||
$item['title'] = $title;
|
||||
$item['uri'] = $uri;
|
||||
$item['timestamp'] = strtotime($extractedDate);
|
||||
$item['timestamp'] = substr($date, 0, -3);
|
||||
$item['author'] = 'Binance';
|
||||
$item['content'] = $content;
|
||||
|
||||
@@ -68,8 +63,7 @@ class BinanceBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
protected function collectAnnouncementData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not fetch Zendesk announcement data.');
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
foreach($html->find('a.article-list-link') as $a) {
|
||||
$title = $a->innertext;
|
||||
|
@@ -1,119 +0,0 @@
|
||||
<?php
|
||||
|
||||
class BingSearchBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bing search';
|
||||
const URI = 'https://www.bing.com/';
|
||||
const DESCRIPTION = 'Return images from bing search discover';
|
||||
const MAINTAINER = 'DnAp';
|
||||
const PARAMETERS = array(
|
||||
'Image Discover' => array(
|
||||
'category' => array(
|
||||
'name' => 'Categories',
|
||||
'type' => 'list',
|
||||
'values' => self::IMAGE_DISCOVER_CATEGORIES
|
||||
),
|
||||
'image_size' => array(
|
||||
'name' => 'Image size',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Small' => 'turl',
|
||||
'Full size' => 'imgurl'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const IMAGE_DISCOVER_CATEGORIES = array(
|
||||
'Abstract' => 'abstract',
|
||||
'Animals' => 'animals',
|
||||
'Anime' => 'anime',
|
||||
'Architecture' => 'architecture',
|
||||
'Arts and Crafts' => 'arts-and-crafts',
|
||||
'Beauty' => 'beauty',
|
||||
'Cars and Motorcycles' => 'cars-and-motorcycles',
|
||||
'Cats' => 'cats',
|
||||
'Celebrities' => 'celebrities',
|
||||
'Comics' => 'comics',
|
||||
'DIY' => 'diy',
|
||||
'Dogs' => 'dogs',
|
||||
'Fitness' => 'fitness',
|
||||
'Food and Drink' => 'food-and-drink',
|
||||
'Funny' => 'funny',
|
||||
'Gadgets' => 'gadgets',
|
||||
'Gardening' => 'gardening',
|
||||
'Geeky' => 'geeky',
|
||||
'Hairstyles' => 'hairstyles',
|
||||
'Home Decor' => 'home-decor',
|
||||
'Marine Life' => 'marine-life',
|
||||
'Men\'s Fashion' => 'men%27s-fashion',
|
||||
'Nature' => 'nature',
|
||||
'Outdoors' => 'outdoors',
|
||||
'Parenting' => 'parenting',
|
||||
'Phone Wallpapers' => 'phone-wallpapers',
|
||||
'Photography' => 'photography',
|
||||
'Quotes' => 'quotes',
|
||||
'Recipes' => 'recipes',
|
||||
'Snow' => 'snow',
|
||||
'Tattoos' => 'tattoos',
|
||||
'Travel' => 'travel',
|
||||
'Video Games' => 'video-games',
|
||||
'Weddings' => 'weddings',
|
||||
'Women\'s Fashion' => 'women%27s-fashion',
|
||||
);
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.bing.com/sa/simg/bing_p_rr_teal_min.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$this->items = $this->imageDiscover($this->getInput('category'));
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if ($this->getInput('category')) {
|
||||
if (self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')] !== null) {
|
||||
$category = self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')];
|
||||
} else {
|
||||
$category = 'Unknown';
|
||||
}
|
||||
|
||||
return 'Best ' . $category . ' - Bing Image Discover';
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function imageDiscover($category)
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI . '/discover/' . $category)
|
||||
or returnServerError('Could not request ' . self::NAME);
|
||||
$sizeKey = $this->getInput('image_size');
|
||||
|
||||
$items = [];
|
||||
foreach ($html->find('a.iusc') as $element) {
|
||||
$data = json_decode(htmlspecialchars_decode($element->getAttribute('m')), true);
|
||||
|
||||
$item = array();
|
||||
$item['title'] = basename(rtrim($data['imgurl'], '/'));
|
||||
$item['uri'] = $data['imgurl'];
|
||||
$item['content'] = '<a href="' . $data['imgurl'] . '">
|
||||
<img src="' . $data[$sizeKey] . '" alt="' . $item['title'] . '"></a>
|
||||
<p>Source: <a href="' . $this->curUrl($data['surl']) . '"> </a></p>';
|
||||
$item['enclosures'] = $data['imgurl'];
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function curUrl($url)
|
||||
{
|
||||
if (strlen($url) <= 80) {
|
||||
return $url;
|
||||
}
|
||||
return substr($url, 0, 80) . '...';
|
||||
}
|
||||
}
|
@@ -13,8 +13,7 @@ class BlaguesDeMerdeBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request BDM.');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach($html->find('div.blague') as $element) {
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
@@ -1,69 +0,0 @@
|
||||
<?php
|
||||
class BloombergBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bloomberg';
|
||||
const URI = 'https://www.bloomberg.com/';
|
||||
const DESCRIPTION = 'Trending stories from Bloomberg';
|
||||
const MAINTAINER = 'mdemoss';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Trending Stories' => array(),
|
||||
'From Search' => array(
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getName()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
case 'Trending Stories':
|
||||
return self::NAME . ' Trending Stories';
|
||||
case 'From Search':
|
||||
if (!is_null($this->getInput('q'))) {
|
||||
return self::NAME . ' Search : ' . $this->getInput('q');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.bwbx.io/s3/javelin/public/hub/images/favicon-black-63fe5249d3.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
case 'Trending Stories': // Get list of top new <article>s from the front page.
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 300);
|
||||
$stories = $html->find('ul.top-news-v3__stories article.top-news-v3-story');
|
||||
break;
|
||||
case 'From Search': // Get list of <article> elements from search.
|
||||
$html = getSimpleHTMLDOMCached(
|
||||
$this->getURI() .
|
||||
'search?sort=time:desc&page=1&query=' .
|
||||
urlencode($this->getInput('q')), 300
|
||||
);
|
||||
$stories = $html->find('div.search-result-items article.search-result-story');
|
||||
break;
|
||||
}
|
||||
foreach ($stories as $element) {
|
||||
$item['uri'] = $element->find('h1 a', 0)->href;
|
||||
if (preg_match('#^https://#i', $item['uri']) !== 1) {
|
||||
$item['uri'] = $this->getURI() . $item['uri'];
|
||||
}
|
||||
$articleHtml = getSimpleHTMLDOMCached($item['uri']);
|
||||
if (!$articleHtml) {
|
||||
continue;
|
||||
}
|
||||
$item['title'] = $element->find('h1 a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($articleHtml->find('meta[name=iso-8601-publish-date],meta[name=date]', 0)->content);
|
||||
$item['content'] = $articleHtml->find('meta[name=description]', 0)->content;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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',
|
||||
),
|
||||
@@ -42,8 +44,7 @@ class BrutBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$results = $html->find('div.results', 0);
|
||||
|
||||
@@ -52,8 +53,7 @@ class BrutBridge extends BridgeAbstract {
|
||||
|
||||
$videoPath = self::URI . $li->children(0)->href;
|
||||
|
||||
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600)
|
||||
or returnServerError('Could not request: ' . $videoPath);
|
||||
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600);
|
||||
|
||||
$this->videoImage = $videoPageHtml->find('meta[name="twitter:image"]', 0)->content;
|
||||
|
||||
|
218
bridges/BukowskisBridge.php
Executable file
218
bridges/BukowskisBridge.php
Executable file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
class BukowskisBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bukowskis';
|
||||
const URI = 'https://www.bukowskis.com';
|
||||
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
|
||||
const MAINTAINER = 'Qluxzz';
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All categories' => '',
|
||||
'Art' => array(
|
||||
'All' => 'art',
|
||||
'Classic Art' => 'art.classic-art',
|
||||
'Classic Finnish Art' => 'art.classic-finnish-art',
|
||||
'Classic Swedish Art' => 'art.classic-swedish-art',
|
||||
'Contemporary' => 'art.contemporary',
|
||||
'Modern Finnish Art' => 'art.modern-finnish-art',
|
||||
'Modern International Art' => 'art.modern-international-art',
|
||||
'Modern Swedish Art' => 'art.modern-swedish-art',
|
||||
'Old Masters' => 'art.old-masters',
|
||||
'Other' => 'art.other',
|
||||
'Photographs' => 'art.photographs',
|
||||
'Prints' => 'art.prints',
|
||||
'Sculpture' => 'art.sculpture',
|
||||
'Swedish Old Masters' => 'art.swedish-old-masters',
|
||||
),
|
||||
'Asian Ceramics & Works of Art' => array(
|
||||
'All' => 'asian-ceramics-works-of-art',
|
||||
'Other' => 'asian-ceramics-works-of-art.other',
|
||||
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
|
||||
),
|
||||
'Books & Manuscripts' => array(
|
||||
'All' => 'books-manuscripts',
|
||||
'Books' => 'books-manuscripts.books',
|
||||
),
|
||||
'Carpets, rugs & textiles' => array(
|
||||
'All' => 'carpets-rugs-textiles',
|
||||
'European' => 'carpets-rugs-textiles.european',
|
||||
'Oriental' => 'carpets-rugs-textiles.oriental',
|
||||
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
|
||||
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
|
||||
),
|
||||
'Ceramics & porcelain' => array(
|
||||
'All' => 'ceramics-porcelain',
|
||||
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
|
||||
'European' => 'ceramics-porcelain.european',
|
||||
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
|
||||
'Scandinavian' => 'ceramics-porcelain.scandinavian',
|
||||
),
|
||||
'Collectibles' => array(
|
||||
'All' => 'collectibles',
|
||||
'Advertising & Retail' => 'collectibles.advertising-retail',
|
||||
'Memorabilia' => 'collectibles.memorabilia',
|
||||
'Movies & music' => 'collectibles.movies-music',
|
||||
'Other' => 'collectibles.other',
|
||||
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
|
||||
'Technica & Nautica' => 'collectibles.technica-nautica',
|
||||
'Toys' => 'collectibles.toys',
|
||||
),
|
||||
'Design' => array(
|
||||
'All' => 'design',
|
||||
'Art glass' => 'design.art-glass',
|
||||
'Furniture' => 'design.furniture',
|
||||
'Other' => 'design.other',
|
||||
),
|
||||
'Folk art' => array(
|
||||
'All' => 'folk-art',
|
||||
'All categories' => 'lots',
|
||||
),
|
||||
'Furniture' => array(
|
||||
'All' => 'furniture',
|
||||
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
|
||||
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
|
||||
'Chairs' => 'furniture.chairs',
|
||||
'Garden furniture' => 'furniture.garden-furniture',
|
||||
'Mirrors' => 'furniture.mirrors',
|
||||
'Other' => 'furniture.other',
|
||||
'Shelves & Book cases' => 'furniture.shelves-book-cases',
|
||||
'Tables' => 'furniture.tables',
|
||||
),
|
||||
'Glassware' => array(
|
||||
'All' => 'glassware',
|
||||
'Glassware' => 'glassware.glassware',
|
||||
'Other' => 'glassware.other',
|
||||
),
|
||||
'Jewellery' => array(
|
||||
'All' => 'jewellery',
|
||||
'Bracelets' => 'jewellery.bracelets',
|
||||
'Brooches' => 'jewellery.brooches',
|
||||
'Earrings' => 'jewellery.earrings',
|
||||
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
|
||||
'Other' => 'jewellery.other',
|
||||
'Rings' => 'jewellery.rings',
|
||||
),
|
||||
'Lighting' => array(
|
||||
'All' => 'lighting',
|
||||
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
|
||||
'Ceiling lights' => 'lighting.ceiling-lights',
|
||||
'Chandeliers' => 'lighting.chandeliers',
|
||||
'Floor lights' => 'lighting.floor-lights',
|
||||
'Other' => 'lighting.other',
|
||||
'Table lights' => 'lighting.table-lights',
|
||||
'Wall lights' => 'lighting.wall-lights',
|
||||
),
|
||||
'Militaria' => array(
|
||||
'All' => 'militaria',
|
||||
'Honors & Medals' => 'militaria.honors-medals',
|
||||
'Other militaria' => 'militaria.other-militaria',
|
||||
'Weaponry' => 'militaria.weaponry',
|
||||
),
|
||||
'Miscellaneous' => array(
|
||||
'All' => 'miscellaneous',
|
||||
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
|
||||
'Nickel silver' => 'miscellaneous.nickel-silver',
|
||||
'Oriental' => 'miscellaneous.oriental',
|
||||
'Other' => 'miscellaneous.other',
|
||||
),
|
||||
'Silver' => array(
|
||||
'All' => 'silver',
|
||||
'Candle sticks' => 'silver.candle-sticks',
|
||||
'Cups & Bowls' => 'silver.cups-bowls',
|
||||
'Cutlery' => 'silver.cutlery',
|
||||
'Other' => 'silver.other',
|
||||
),
|
||||
'Timepieces' => array(
|
||||
'All' => 'timepieces',
|
||||
'Other' => 'timepieces.other',
|
||||
'Pocket watches' => 'timepieces.pocket-watches',
|
||||
'Table clocks' => 'timepieces.table-clocks',
|
||||
'Wrist watches' => 'timepieces.wrist-watches',
|
||||
),
|
||||
'Vintage & Fashion' => array(
|
||||
'All' => 'vintage-fashion',
|
||||
'Accessories' => 'vintage-fashion.accessories',
|
||||
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
|
||||
'Clothes' => 'vintage-fashion.clothes',
|
||||
),
|
||||
)
|
||||
),
|
||||
'sort_order' => array(
|
||||
'name' => 'Sort order',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Ending soon' => 'ending',
|
||||
'Most recent' => 'recent',
|
||||
'Most bids' => 'most',
|
||||
'Fewest bids' => 'fewest',
|
||||
'Lowest price' => 'lowest',
|
||||
'Highest price' => 'highest',
|
||||
'Lowest estimate' => 'low',
|
||||
'Highest estimate' => 'high',
|
||||
'Alphabetical' => 'alphabetical',
|
||||
),
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Swedish' => 'sv',
|
||||
'Finnish' => 'fi'
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
private $title;
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$baseUrl = 'https://www.bukowskis.com';
|
||||
$category = $this->getInput('category');
|
||||
$language = $this->getInput('language');
|
||||
$sort_order = $this->getInput('sort_order');
|
||||
|
||||
$url = $baseUrl . '/' . $language . '/lots';
|
||||
|
||||
if ($category)
|
||||
$url = $url . '/category/' . $category;
|
||||
|
||||
if ($sort_order)
|
||||
$url = $url . '/sort/' . $sort_order;
|
||||
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
|
||||
|
||||
foreach ($html->find('div.c-lot-index-lot') as $lot) {
|
||||
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
|
||||
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
|
||||
$images = json_decode(
|
||||
htmlspecialchars_decode(
|
||||
$lot
|
||||
->find('img.o-aspect-ratio__image', 0)
|
||||
->getAttribute('data-thumbnails')
|
||||
)
|
||||
);
|
||||
|
||||
$this->items[] = array(
|
||||
'title' => $title,
|
||||
'uri' => $baseUrl . $relative_url,
|
||||
'uid' => $lot->getAttribute('data-lot-id'),
|
||||
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
|
||||
'enclosures' => array_slice($images, 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
}
|
@@ -41,8 +41,7 @@ class BundesbankBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No response for ' . $this->getURI());
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
|
@@ -53,8 +53,7 @@ class CNETBridge extends BridgeAbstract {
|
||||
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request CNET: ' . $pageUrl);
|
||||
$html = getSimpleHTMLDOM($pageUrl);
|
||||
|
||||
// Process articles
|
||||
foreach($html->find('div.assetBody, div.riverPost') as $element) {
|
||||
|
@@ -23,8 +23,8 @@ class CNETFranceBridge extends FeedExpander
|
||||
)
|
||||
);
|
||||
|
||||
private $bannedTitle = [];
|
||||
private $bannedURL = [];
|
||||
private $bannedTitle = array();
|
||||
private $bannedURL = array();
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
|
140
bridges/CVEDetailsBridge.php
Normal file
140
bridges/CVEDetailsBridge.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
// CVE Details is a collection of CVEs, taken from the National Vulnerability
|
||||
// Database (NVD) and other sources like the Exploit DB and Metasploit. The
|
||||
// website categorizes it by vendor and product and attach the CWE category.
|
||||
// There is a Atom feed available, but only logged in users can use it,
|
||||
// it is not reliable and contain no useful information. This bridge create a
|
||||
// sane feed with additional information like tags and a link to the CWE
|
||||
// a description of the vulnerability.
|
||||
class CVEDetailsBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'Aaron Fischer';
|
||||
const NAME = 'CVE Details';
|
||||
const CACHE_TIMEOUT = 60 * 60 * 6; // 6 hours
|
||||
const DESCRIPTION = 'Report new CVE vulnerabilities for a given vendor (and product)';
|
||||
const URI = 'https://www.cvedetails.com';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
// The Vendor ID can be taken from the URL
|
||||
'vendor_id' => array(
|
||||
'name' => 'Vendor ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => 74, // PHP
|
||||
),
|
||||
// The optional Product ID can be taken from the URL as well
|
||||
'product_id' => array(
|
||||
'name' => 'Product ID',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'exampleValue' => 128, // PHP
|
||||
),
|
||||
));
|
||||
|
||||
private $html = null;
|
||||
private $vendor = '';
|
||||
private $product = '';
|
||||
|
||||
// Return the URL to query.
|
||||
// Because of the optional product ID, we need to attach it if it is
|
||||
// set. The search result page has the exact same structure (with and
|
||||
// without the product ID).
|
||||
private function _buildURL() {
|
||||
$url = self::URI . '/vulnerability-list/vendor_id-' . $this->getInput('vendor_id');
|
||||
if ($this->getInput('product_id') !== '') {
|
||||
$url .= '/product_id-' . $this->getInput('product_id');
|
||||
}
|
||||
// Sadly, there is no way (prove me wrong please) to sort the search
|
||||
// result by publish date. So the nearest alternative is the CVE
|
||||
// number, which should be mostly accurate.
|
||||
$url .= '?order=1'; // Order by CVE number DESC
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Make the actual request to cvedetails.com and stores the response
|
||||
// (HTML) for later use and extract vendor and product from it.
|
||||
private function _fetchContent() {
|
||||
$html = getSimpleHTMLDOM($this->_buildURL());
|
||||
$this->html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$vendor = $html->find('#contentdiv > h1 > a', 0);
|
||||
if ($vendor == null) {
|
||||
returnServerError('Invalid Vendor ID ' .
|
||||
$this->getInput('vendor_id') .
|
||||
' or Product ID ' .
|
||||
$this->getInput('product_id'));
|
||||
}
|
||||
$this->vendor = $vendor->innertext;
|
||||
|
||||
$product = $html->find('#contentdiv > h1 > a', 1);
|
||||
if ($product != null) {
|
||||
$this->product = $product->innertext;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the name of the feed.
|
||||
public function getName() {
|
||||
if ($this->getInput('vendor_id') == '') {
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
if ($this->html == null) {
|
||||
$this->_fetchContent();
|
||||
}
|
||||
|
||||
$name = 'CVE Vulnerabilities for ' . $this->vendor;
|
||||
if ($this->product != '') {
|
||||
$name .= '/' . $this->product;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Pull the data from the HTML response and fill the items..
|
||||
public function collectData() {
|
||||
if ($this->html == null) {
|
||||
$this->_fetchContent();
|
||||
}
|
||||
|
||||
foreach ($this->html->find('#vulnslisttable .srrowns') as $i => $tr) {
|
||||
// There are some optional vulnerability types, which will be
|
||||
// added to the categories as well as the CWE number -- which is
|
||||
// always given.
|
||||
$categories = array($this->vendor);
|
||||
$enclosures = array();
|
||||
|
||||
$cwe = $tr->find('td', 2)->find('a', 0);
|
||||
if ($cwe != null) {
|
||||
$cwe = $cwe->innertext;
|
||||
$categories[] = 'CWE-' . $cwe;
|
||||
$enclosures[] = 'https://cwe.mitre.org/data/definitions/' . $cwe . '.html';
|
||||
}
|
||||
$c = $tr->find('td', 4)->innertext;
|
||||
if (trim($c) != '') {
|
||||
$categories[] = $c;
|
||||
}
|
||||
if ($this->product != '') {
|
||||
$categories[] = $this->product;
|
||||
}
|
||||
|
||||
// The CVE number itself
|
||||
$title = $tr->find('td', 1)->find('a', 0)->innertext;
|
||||
|
||||
$this->items[] = array(
|
||||
'uri' => $tr->find('td', 1)->find('a', 0)->href,
|
||||
'title' => $title,
|
||||
'timestamp' => $tr->find('td', 5)->innertext,
|
||||
'content' => $tr->next_sibling()->innertext,
|
||||
'categories' => $categories,
|
||||
'enclosures' => $enclosures,
|
||||
'uid' => $tr->find('td', 1)->find('a', 0)->innertext,
|
||||
);
|
||||
|
||||
// We only want to fetch the latest 10 CVEs
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -22,7 +22,7 @@ class CachetBridge extends BridgeAbstract {
|
||||
);
|
||||
const CACHE_TIMEOUT = 300;
|
||||
|
||||
private $componentCache = [];
|
||||
private $componentCache = array();
|
||||
|
||||
public function getURI() {
|
||||
return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host');
|
||||
@@ -114,13 +114,13 @@ class CachetBridge extends BridgeAbstract {
|
||||
$uidOrig = $permalink . $incident->created_at;
|
||||
$uid = hash('sha512', $uidOrig);
|
||||
$timestamp = strtotime($incident->created_at);
|
||||
$categories = [];
|
||||
$categories = array();
|
||||
$categories[] = $incident->human_status;
|
||||
if ($componentName !== '') {
|
||||
$categories[] = $componentName;
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $permalink;
|
||||
$item['title'] = $title;
|
||||
$item['timestamp'] = $timestamp;
|
||||
|
@@ -2,7 +2,7 @@
|
||||
class CastorusBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const NAME = 'Castorus Bridge';
|
||||
const URI = 'http://www.castorus.com';
|
||||
const URI = 'https://www.castorus.com';
|
||||
const CACHE_TIMEOUT = 600; // 10min
|
||||
const DESCRIPTION = 'Returns the latest changes';
|
||||
|
||||
|
83
bridges/CeskaTelevizeBridge.php
Normal file
83
bridges/CeskaTelevizeBridge.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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-]+\/)(bonus\/)?$/';
|
||||
if (!preg_match($validUrl, $url, $match)) {
|
||||
returnServerError('Invalid url');
|
||||
}
|
||||
|
||||
$category = isset($match[4]) ? $match[4] : 'nove';
|
||||
$fixedUrl = "{$match[1]}dily/{$category}/";
|
||||
|
||||
$html = getSimpleHTMLDOM($fixedUrl);
|
||||
|
||||
$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('#episodeListSection a[data-testid=next-link]') as $element) {
|
||||
$itemTitle = $element->find('h3', 0);
|
||||
$itemContent = $element->find('div[class^=content-]', 0);
|
||||
$itemDate = $element->find('div[class^=playTime-] span', 0);
|
||||
$itemThumbnail = $element->find('img', 0);
|
||||
$itemUri = self::URI . $element->getAttribute('href');
|
||||
|
||||
$item = array(
|
||||
'title' => $this->fixChars($itemTitle->plaintext),
|
||||
'uri' => $itemUri,
|
||||
'content' => '<img src="' . $itemThumbnail->getAttribute('src') . '" /><br />'
|
||||
. $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;
|
||||
}
|
||||
}
|
||||
}
|
387
bridges/CodebergBridge.php
Normal file
387
bridges/CodebergBridge.php
Normal file
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
class CodebergBridge extends BridgeAbstract {
|
||||
const NAME = 'Codeberg Bridge';
|
||||
const URI = 'https://codeberg.org/';
|
||||
const DESCRIPTION = 'Returns commits, issues, pull requests or releases for a repository.';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'Commits' => array(
|
||||
'branch' => array(
|
||||
'name' => 'branch',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'main',
|
||||
'required' => false,
|
||||
'title' => 'Optional, main branch is used by default.',
|
||||
),
|
||||
),
|
||||
'Issues' => array(),
|
||||
'Issue Comments' => array(
|
||||
'issueId' => array(
|
||||
'name' => 'Issue ID',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
)
|
||||
),
|
||||
'Pull Requests' => array(),
|
||||
'Releases' => array(),
|
||||
'global' => array(
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'username',
|
||||
'title' => 'Username of account that the repository belongs to.',
|
||||
'required' => true,
|
||||
),
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'repo',
|
||||
'required' => true,
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
|
||||
const TEST_DETECT_PARAMETERS = array(
|
||||
'https://codeberg.org/Codeberg/Community/issues/507' => array(
|
||||
'context' => 'Issue Comments', 'username' => 'Codeberg', 'repo' => 'Community', 'issueId' => '507'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/issues' => array(
|
||||
'context' => 'Issues', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/pulls' => array(
|
||||
'context' => 'Pull Requests', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/releases' => array(
|
||||
'context' => 'Releases', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/commits/branch/master' => array(
|
||||
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community', 'branch' => 'master'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/commits' => array(
|
||||
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
)
|
||||
);
|
||||
|
||||
private $defaultBranch = 'main';
|
||||
private $issueTitle = '';
|
||||
|
||||
private $urlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)(?:\/commits\/branch\/([\w]+))?/';
|
||||
private $issuesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues/';
|
||||
private $pullsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/pulls/';
|
||||
private $releasesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/releases/';
|
||||
private $issueCommentsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues\/([0-9]+)/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// Issue Comments
|
||||
if(preg_match($this->issueCommentsUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Issue Comments';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
$params['issueId'] = $matches[3];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Issues
|
||||
if(preg_match($this->issuesUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Issues';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Pull Requests
|
||||
if(preg_match($this->pullsUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Pull Requests';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Releases
|
||||
if(preg_match($this->releasesUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Releases';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Commits
|
||||
if(preg_match($this->urlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Commits';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
if (isset($matches[3])) {
|
||||
$params['branch'] = $matches[3];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
$this->extractCommits($html);
|
||||
break;
|
||||
case 'Issues':
|
||||
$this->extractIssues($html);
|
||||
break;
|
||||
case 'Issue Comments':
|
||||
$this->extractIssueComments($html);
|
||||
break;
|
||||
case 'Pull Requests':
|
||||
$this->extractPulls($html);
|
||||
break;
|
||||
case 'Releases':
|
||||
$this->extractReleases($html);
|
||||
break;
|
||||
default:
|
||||
returnClientError('Invalid context: ' . $this->queriedContext);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
if ($this->getBranch() === $this->defaultBranch) {
|
||||
return $this->getRepo() . ' Commits';
|
||||
}
|
||||
|
||||
return $this->getRepo() . ' Commits (' . $this->getBranch() . ' branch) - ' . self::NAME;
|
||||
case 'Issues':
|
||||
return $this->getRepo() . ' Issues - ' . self::NAME;
|
||||
case 'Issue Comments':
|
||||
return $this->issueTitle . ' - Issue Comments - ' . self::NAME;
|
||||
case 'Pull Requests':
|
||||
return $this->getRepo() . ' Pull Requests - ' . self::NAME;
|
||||
case 'Releases':
|
||||
return $this->getRepo() . ' Releases - ' . self::NAME;
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
return self::URI . $this->getRepo() . '/commits/branch/' . $this->getBranch();
|
||||
case 'Issues':
|
||||
return self::URI . $this->getRepo() . '/issues/';
|
||||
case 'Issue Comments':
|
||||
return self::URI . $this->getRepo() . '/issues/' . $this->getInput('issueId');
|
||||
case 'Pull Requests':
|
||||
return self::URI . $this->getRepo() . '/pulls';
|
||||
case 'Releases':
|
||||
return self::URI . $this->getRepo() . '/releases';
|
||||
default:
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
private function getBranch() {
|
||||
if ($this->getInput('branch')) {
|
||||
return $this->getInput('branch');
|
||||
}
|
||||
|
||||
return $this->defaultBranch;
|
||||
}
|
||||
|
||||
private function getRepo() {
|
||||
return $this->getInput('username') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
private function extractCommits($html) {
|
||||
$table = $html->find('table#commits-table', 0);
|
||||
$tbody = $table->find('tbody.commit-list', 0);
|
||||
|
||||
foreach ($tbody->find('tr') as $tr) {
|
||||
$item = array();
|
||||
|
||||
$message = $tr->find('td.message', 0);
|
||||
|
||||
$item['title'] = $message->find('span.message-wrapper', 0)->plaintext;
|
||||
$item['uri'] = $tr->find('td.sha', 0)->find('a', 0)->href;
|
||||
$item['author'] = $tr->find('td.author', 0)->plaintext;
|
||||
$item['timestamp'] = $tr->find('td', 3)->find('span', 0)->title;
|
||||
|
||||
if ($message->find('pre.commit-body', 0)) {
|
||||
$message->find('pre.commit-body', 0)->style = '';
|
||||
|
||||
$item['content'] = $message->find('pre.commit-body', 0);
|
||||
} else {
|
||||
$item['content'] = '<blockquote>' . $item['title'] . '</blockquote>';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractIssues($html) {
|
||||
$div = $html->find('div.repository', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = array();
|
||||
|
||||
$number = $li->find('div', 0)->plaintext;
|
||||
|
||||
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
|
||||
$item['uri'] = $li->find('a.title', 0)->href;
|
||||
$item['timestamp'] = $li->find('p.desc', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('p.desc', 0)->find('a', 0)->plaintext;
|
||||
|
||||
// Fetch issue page
|
||||
$issuePage = getSimpleHTMLDOMCached($item['uri'], 3600);
|
||||
|
||||
$issuePage = defaultLinkTo($issuePage, self::URI);
|
||||
|
||||
$item['content'] = $issuePage->find('ui.timeline', 0)->find('div.render-content.markdown', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractIssueComments($html) {
|
||||
$this->issueTitle = $html->find('span#issue-title', 0)->plaintext
|
||||
. ' (' . $html->find('span.index', 0)->plaintext . ')';
|
||||
|
||||
foreach ($html->find('ui.timeline > div.timeline-item.comment') as $div) {
|
||||
$item = array();
|
||||
|
||||
if ($div->class === 'timeline-item comment merge box') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = $this->ellipsisTitle($div->find('div.render-content.markdown', 0)->plaintext);
|
||||
$item['uri'] = $div->find('span.text.grey', 0)->find('a', 1)->href;
|
||||
$item['content'] = $div->find('div.render-content.markdown', 0);
|
||||
|
||||
if ($div->find('div.dropzone-attachments', 0)) {
|
||||
$item['content'] .= $div->find('div.dropzone-attachments', 0);
|
||||
}
|
||||
|
||||
$item['author'] = $div->find('a.author', 0)->innertext;
|
||||
$item['timestamp'] = $div->find('span.time-since', 0)->title;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractPulls($html) {
|
||||
$div = $html->find('div.repository', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = array();
|
||||
|
||||
$number = $li->find('div', 0)->plaintext;
|
||||
|
||||
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
|
||||
$item['uri'] = $li->find('a.title', 0)->href;
|
||||
$item['timestamp'] = $li->find('p.desc', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('p.desc', 0)->find('a', 0)->plaintext;
|
||||
|
||||
// Fetch pull request page
|
||||
$pullRequestPage = getSimpleHTMLDOMCached($item['uri'], 3600);
|
||||
|
||||
$pullRequestPage = defaultLinkTo($pullRequestPage, self::URI);
|
||||
|
||||
$item['content'] = $pullRequestPage->find('ui.timeline', 0)->find('div.render-content.markdown', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractReleases($html) {
|
||||
$ul = $html->find('ul#release-list', 0);
|
||||
|
||||
foreach ($ul->find('li.ui.grid') as $li) {
|
||||
$item = array();
|
||||
|
||||
if ($li->find('h3', 0)) { // Release
|
||||
$item['title'] = $li->find('h3', 0)->plaintext;
|
||||
$item['uri'] = $li->find('h3', 0)->find('a', 0)->href;
|
||||
|
||||
$tag = $li->find('span.tag', 0)->find('a', 0);
|
||||
$commit = $li->find('span.commit', 0);
|
||||
$downloads = $this->extractDownloads($li->find('div.download', 0));
|
||||
|
||||
$item['content'] = $li->find('div.markdown', 0);
|
||||
$item['content'] .= <<<HTML
|
||||
<strong>Tag</strong>
|
||||
<p>{$tag}</p>
|
||||
<strong>Commit</strong>
|
||||
<p>{$commit}</p>
|
||||
{$downloads}
|
||||
HTML;
|
||||
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('span.author', 0)->find('a', 0)->plaintext;
|
||||
}
|
||||
|
||||
if ($li->find('h4', 0)) { // Tag
|
||||
$item['title'] = $li->find('h4', 0)->plaintext;
|
||||
$item['uri'] = $li->find('h4', 0)->find('a', 0)->href;
|
||||
|
||||
$item['content'] = <<<HTML
|
||||
<strong>Commit</strong>
|
||||
<p>{$li->find('div.download', 0)->find('a', 0)}</p>
|
||||
HTML;
|
||||
|
||||
$item['content'] .= $this->extractDownloads($li->find('div.download', 0), true);
|
||||
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractDownloads($html, $skipFirst = false) {
|
||||
$downloads = '';
|
||||
|
||||
foreach ($html->find('a') as $index => $a) {
|
||||
if ($skipFirst === true && $index === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$downloads .= <<<HTML
|
||||
{$a}<br>
|
||||
HTML;
|
||||
}
|
||||
|
||||
return <<<EOD
|
||||
<strong>Downloads</strong>
|
||||
<p>{$downloads}</p>
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function ellipsisTitle($text) {
|
||||
$length = 100;
|
||||
|
||||
if (strlen($text) > $length) {
|
||||
$text = explode('<br>', wordwrap($text, $length, '<br>'));
|
||||
return $text[0] . '...';
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ class CollegeDeFranceBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pit-fgfjiudghdf';
|
||||
const NAME = 'CollegeDeFrance';
|
||||
const URI = 'http://www.college-de-france.fr/';
|
||||
const URI = 'https://www.college-de-france.fr/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const DESCRIPTION = 'Returns the latest audio and video from CollegeDeFrance';
|
||||
|
||||
@@ -34,8 +34,7 @@ class CollegeDeFranceBridge extends BridgeAbstract {
|
||||
* </li>
|
||||
*/
|
||||
$html = getSimpleHTMLDOM(self::URI
|
||||
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all')
|
||||
or returnServerError('Could not request CollegeDeFrance.');
|
||||
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all');
|
||||
|
||||
foreach($html->find('a[data-target]') as $element) {
|
||||
$item = array();
|
||||
|
@@ -7,8 +7,11 @@ class ComboiosDePortugalBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'somini';
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos')
|
||||
or returnServerError('Could not load content');
|
||||
# Do not verify SSL certificate (the server doesn't send the intermediate)
|
||||
# https://github.com/RSS-Bridge/rss-bridge/issues/2397
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos', array(), array(
|
||||
CURLOPT_SSL_VERIFYPEER => 0,
|
||||
));
|
||||
|
||||
foreach($html->find('.warnings-table a') as $element) {
|
||||
$item = array();
|
||||
|
62
bridges/ComicsKingdomBridge.php
Normal file
62
bridges/ComicsKingdomBridge.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
class ComicsKingdomBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'stjohnjohnson';
|
||||
const NAME = 'Comics Kingdom Unofficial RSS';
|
||||
const URI = 'https://www.comicskingdom.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Comics Kingdom Unofficial RSS';
|
||||
const PARAMETERS = array( array(
|
||||
'comicname' => array(
|
||||
'name' => 'comicname',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI(), array(), array(), true, false);
|
||||
|
||||
// Get author from first page
|
||||
$author = $html->find('div.author p', 0);;
|
||||
|
||||
// Get current date/link
|
||||
$link = $html->find('meta[property=og:url]', 0)->content;
|
||||
for($i = 0; $i < 5; $i++) {
|
||||
$item = array();
|
||||
|
||||
$page = getSimpleHTMLDOM($link);
|
||||
|
||||
$imagelink = $page->find('meta[property=og:image]', 0)->content;
|
||||
$prevSlug = $page->find('slider-arrow[:is-left-arrow=true]', 0);
|
||||
$link = $this->getURI() . '/' . $prevSlug->getAttribute('date-slug');
|
||||
|
||||
$date = explode('/', $link);
|
||||
|
||||
$item['id'] = $imagelink;
|
||||
$item['uri'] = $link;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = 'Comics Kingdom ' . $this->getInput('comicname');
|
||||
$item['timestamp'] = DateTime::createFromFormat('Y-m-d', $date[count($date) - 1])->getTimestamp();
|
||||
$item['content'] = '<img src="' . $imagelink . '" />';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('comicname'))) {
|
||||
return self::URI . urlencode($this->getInput('comicname'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('comicname'))) {
|
||||
return $this->getInput('comicname') . ' - Comics Kingdom';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -10,24 +10,23 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||
const BETA = 'beta';
|
||||
const ALPHA = 'alpha';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'channel' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'channel' => array(
|
||||
'name' => 'Release Channel',
|
||||
'type' => 'list',
|
||||
'defaultValue' => self::STABLE,
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Stable' => self::STABLE,
|
||||
'Beta' => self::BETA,
|
||||
'Alpha' => self::ALPHA,
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function getReleaseFeed($jsonUrl) {
|
||||
$json = getContents($jsonUrl)
|
||||
or returnServerError('Could not request Core OS Website.');
|
||||
$json = getContents($jsonUrl);
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
@@ -39,7 +38,7 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||
$data = $this->getReleaseFeed($this->getJsonUri());
|
||||
|
||||
foreach ($data as $releaseVersion => $release) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = "https://coreos.com/releases/#$releaseVersion";
|
||||
$item['title'] = $releaseVersion;
|
||||
|
@@ -8,8 +8,7 @@ class CopieDoubleBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'CopieDouble';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request CopieDouble.');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$table = $html->find('table table', 2);
|
||||
|
||||
|
@@ -1,55 +1,27 @@
|
||||
<?php
|
||||
class CourrierInternationalBridge extends BridgeAbstract {
|
||||
class CourrierInternationalBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Courrier International Bridge';
|
||||
const URI = 'https://www.courrierinternational.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5 min
|
||||
const DESCRIPTION = 'Courrier International bridge';
|
||||
const DESCRIPTION = 'Returns the newest articles';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error.');
|
||||
$this->collectExpandableDatas(static::URI . 'feed/all/rss.xml', 20);
|
||||
}
|
||||
|
||||
$element = $html->find('article');
|
||||
$article_count = 1;
|
||||
protected function parseItem($feedItem){
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
foreach($element as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->parent->getAttribute('href');
|
||||
|
||||
if(strpos($item['uri'], 'http') === false) {
|
||||
$item['uri'] = self::URI . $item['uri'];
|
||||
}
|
||||
|
||||
$page = getSimpleHTMLDOMCached($item['uri']);
|
||||
|
||||
$content = $page->find('.article-text', 0);
|
||||
|
||||
if(!$content) {
|
||||
$content = $page->find('.depeche-text', 0);
|
||||
}
|
||||
|
||||
$item['content'] = sanitize($content);
|
||||
$item['title'] = strip_tags($article->find('.title', 0));
|
||||
|
||||
$dateTime = date_parse($page->find('time', 0));
|
||||
|
||||
$item['timestamp'] = mktime(
|
||||
$dateTime['hour'],
|
||||
$dateTime['minute'],
|
||||
$dateTime['second'],
|
||||
$dateTime['month'],
|
||||
$dateTime['day'],
|
||||
$dateTime['year']
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
$article_count ++;
|
||||
|
||||
if($article_count > 5)
|
||||
break;
|
||||
$articlePage = getSimpleHTMLDOMCached($feedItem->link);
|
||||
$content = $articlePage->find('.article-text', 0);
|
||||
if(!$content) {
|
||||
$content = $articlePage->find('.depeche-text', 0);
|
||||
}
|
||||
|
||||
$item['content'] = sanitize($content);
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
@@ -17,8 +17,7 @@ class CryptomeBridge extends BridgeAbstract {
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request Cryptome.');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$number = $this->getInput('n');
|
||||
|
||||
|
@@ -19,8 +19,7 @@ class CuriousCatBridge extends BridgeAbstract {
|
||||
|
||||
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
|
||||
|
||||
$apiJson = getContents($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
$apiJson = getContents($url);
|
||||
|
||||
$apiData = json_decode($apiJson, true);
|
||||
|
||||
|
@@ -46,8 +46,7 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
|
||||
if ($this->queriedContext === 'By username' || $this->queriedContext === 'By playlist id') {
|
||||
|
||||
$apiJson = getContents($this->getApiUrl())
|
||||
or returnServerError('Could not request: ' . $this->getApiUrl());
|
||||
$apiJson = getContents($this->getApiUrl());
|
||||
|
||||
$apiData = json_decode($apiJson, true);
|
||||
|
||||
@@ -72,8 +71,7 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
|
||||
if ($this->queriedContext === 'From search results') {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request Dailymotion.');
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
foreach($html->find('div.media a.preview_link') as $element) {
|
||||
$item = array();
|
||||
@@ -180,8 +178,7 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
|
||||
$url = self::URI . 'playlist/' . $id;
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
$title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
return $title;
|
||||
|
@@ -57,8 +57,7 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$content = getContents($this->getFullURI())
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
$content = getContents($this->getFullURI());
|
||||
|
||||
$html = Fix_Simple_Html_Dom::str_get_html($content);
|
||||
|
||||
|
@@ -9,8 +9,7 @@ class DansTonChatBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . 'latest.html')
|
||||
or returnServerError('Could not request DansTonChat.');
|
||||
$html = getSimpleHTMLDOM(self::URI . 'latest.html');
|
||||
|
||||
foreach($html->find('div.item') as $element) {
|
||||
$item = array();
|
||||
|
81
bridges/DarkReadingBridge.php
Normal file
81
bridges/DarkReadingBridge.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
class DarkReadingBridge extends FeedExpander {
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Dark Reading Bridge';
|
||||
const URI = 'https://www.darkreading.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles from Dark Reading';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'feed' => array(
|
||||
'name' => 'Feed',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All Dark Reading Stories' => '000_AllArticles',
|
||||
'Attacks/Breaches' => '644_Attacks/Breaches',
|
||||
'Application Security' => '645_Application%20Security',
|
||||
'Database Security' => '646_Database%20Security',
|
||||
'Cloud' => '647_Cloud',
|
||||
'Endpoint' => '648_Endpoint',
|
||||
'Authentication' => '649_Authentication',
|
||||
'Privacy' => '650_Privacy',
|
||||
'Mobile' => '651_Mobile',
|
||||
'Perimeter' => '652_Perimeter',
|
||||
'Risk' => '653_Risk',
|
||||
'Compliance' => '654_Compliance',
|
||||
'Operations' => '655_Operations',
|
||||
'Careers and People' => '656_Careers%20and%20People',
|
||||
'Identity and Access Management' => '657_Identity%20and%20Access%20Management',
|
||||
'Analytics' => '658_Analytics',
|
||||
'Threat Intelligence' => '659_Threat%20Intelligence',
|
||||
'Security Monitoring' => '660_Security%20Monitoring',
|
||||
'Vulnerabilities / Threats' => '661_Vulnerabilities%20/%20Threats',
|
||||
'Advanced Threats' => '662_Advanced%20Threats',
|
||||
'Insider Threats' => '663_Insider%20Threats',
|
||||
'Vulnerability Management' => '664_Vulnerability%20Management',
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$feed = $this->getInput('feed');
|
||||
$feed_splitted = explode('_', $feed);
|
||||
$feed_id = $feed_splitted[0];
|
||||
$feed_name = $feed_splitted[1];
|
||||
if(empty($feed) || !ctype_digit($feed_id) || !preg_match('/[A-Za-z%20\/]/', $feed_name)) {
|
||||
returnClientError('Invalid feed, please check the "feed" parameter.');
|
||||
}
|
||||
$feed_url = $this->getURI() . 'rss_simple.asp';
|
||||
if ($feed_id != '000') {
|
||||
$feed_url .= '?f_n=' . $feed_id . '&f_ln=' . $feed_name;
|
||||
}
|
||||
$this->collectExpandableDatas($feed_url, 20);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
$article = getSimpleHTMLDOMCached($item['uri']);
|
||||
$item['content'] = $this->extractArticleContent($article);
|
||||
$item['enclosures'] = array(); //remove author profile picture
|
||||
$image = $article->find('meta[property="og:image"]', 0);
|
||||
if (is_object($image)) {
|
||||
$image = $image->content;
|
||||
$item['enclosures'] = array($image);
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractArticleContent($article){
|
||||
$content = $article->find('div.article-content', 0)->innertext;
|
||||
|
||||
foreach (array(
|
||||
'<div class="divsplitter',
|
||||
'<div style="float: left; margin-right: 2px;',
|
||||
'<div class="more-insights',
|
||||
'<div id="more-insights',
|
||||
) as $div_start) {
|
||||
$content = stripRecursiveHTMLSection($content, 'div', $div_start);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
23
bridges/DaveRamseyBlogBridge.php
Normal file
23
bridges/DaveRamseyBlogBridge.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class DaveRamseyBlogBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'johnpc';
|
||||
const NAME = 'Dave Ramsey Blog';
|
||||
const URI = 'https://www.daveramsey.com/blog';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns blog posts from daveramsey.com';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach ($html->find('.Post') as $element) {
|
||||
$this->items[] = array(
|
||||
'uri' => 'https://www.daveramsey.com' . $element->find('header > a', 0)->href,
|
||||
'title' => $element->find('header > h2 > a', 0)->plaintext,
|
||||
'tags' => $element->find('.Post-topic', 0)->plaintext,
|
||||
'content' => $element->find('.Post-body', 0)->plaintext,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -2,7 +2,7 @@
|
||||
class DerpibooruBridge extends BridgeAbstract {
|
||||
const NAME = 'Derpibooru Bridge';
|
||||
const URI = 'https://derpibooru.org/';
|
||||
const DESCRIPTION = 'Returns newest posts from a Derpibooru search';
|
||||
const DESCRIPTION = 'Returns newest images from a Derpibooru search';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const MAINTAINER = 'Roliga';
|
||||
|
||||
@@ -73,40 +73,42 @@ class DerpibooruBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
$queryJson = json_decode(getContents(
|
||||
self::URI
|
||||
. 'search.json?filter_id='
|
||||
. 'api/v1/json/search/images?filter_id='
|
||||
. urlencode($this->getInput('f'))
|
||||
. '&q='
|
||||
. urlencode($this->getInput('q'))
|
||||
)) or returnServerError('Failed to query Derpibooru');
|
||||
));
|
||||
|
||||
foreach($queryJson->search as $post) {
|
||||
foreach($queryJson->images as $post) {
|
||||
$item = array();
|
||||
|
||||
$postUri = self::URI . $post->id;
|
||||
|
||||
$item['uri'] = $postUri;
|
||||
$item['title'] = $post->id;
|
||||
$item['title'] = $post->name;
|
||||
$item['timestamp'] = strtotime($post->created_at);
|
||||
$item['author'] = $post->uploader;
|
||||
$item['enclosures'] = array('https:' . $post->image);
|
||||
$item['categories'] = explode(', ', $post->tags);
|
||||
$item['enclosures'] = array($post->view_url);
|
||||
$item['categories'] = $post->tags;
|
||||
|
||||
$item['content'] = '<p><a href="' // image preview
|
||||
. $postUri
|
||||
. '"><img src="https:'
|
||||
. '"><img src="'
|
||||
. $post->representations->medium
|
||||
. '"></a></p><p>' // description
|
||||
. $post->description
|
||||
. '</p><p><b>Size:</b> ' // image size
|
||||
. $post->width
|
||||
. 'x'
|
||||
. $post->height
|
||||
. '<br><b>Source:</b> <a href="' // source link
|
||||
. $post->height;
|
||||
// source link
|
||||
if ($post->source_url != null) {
|
||||
$item['content'] .= '<br><b>Source:</b> <a href="'
|
||||
. $post->source_url
|
||||
. '">'
|
||||
. $post->source_url
|
||||
. '</a></p>';
|
||||
|
||||
};
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -116,6 +116,12 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
'name' => 'Load full articles',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Enable to load the full article for each item'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 3,
|
||||
'title' => "Maximum number of items to return in the feed.\n0 = unlimited"
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -149,13 +155,14 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
}
|
||||
*/
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$this->title = html_entity_decode($html->find('title', 0)->plaintext, ENT_QUOTES);
|
||||
|
||||
$limit = $this->getInput('limit') ?: 0;
|
||||
|
||||
foreach($html->find('article') as $article) {
|
||||
$item = array();
|
||||
|
||||
@@ -169,13 +176,14 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if ($limit > 0 && count($this->items) >= $limit) break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function getFullNewsArticle($uri) {
|
||||
$html = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Unable to load full article!');
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
@@ -189,8 +197,7 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
* @return void
|
||||
*/
|
||||
private function extractNewsLanguages() {
|
||||
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/about-desoutter/news-events')
|
||||
or returnServerError('Error loading news!');
|
||||
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/about-desoutter/news-events');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
@@ -215,8 +222,7 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
* @return void
|
||||
*/
|
||||
private function extractIndustryLanguages() {
|
||||
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/industry-4-0/news')
|
||||
or returnServerError('Error loading news!');
|
||||
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/industry-4-0/news');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
|
@@ -45,29 +45,21 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
$html = getSimpleHTMLDOMCached($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$articles = $html->find('div[class="single-article"]')
|
||||
$articles = $html->find('div.crayons-story')
|
||||
or returnServerError('Could not find articles!');
|
||||
|
||||
foreach($articles as $article) {
|
||||
|
||||
if($article->find('[class*="cta"]', 0)) { // Skip ads
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
@@ -75,7 +67,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 {
|
||||
@@ -85,19 +76,32 @@ 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() {
|
||||
if (!is_null($this->getInput('tag'))) {
|
||||
return ucfirst($this->getInput('tag')) . ' - dev.to';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getFullArticle($url) {
|
||||
$html = getSimpleHTMLDOMCached($url)
|
||||
or returnServerError('Unable to load article from "' . $url . '"!');
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
83
bridges/DiarioDeNoticiasBridge.php
Normal file
83
bridges/DiarioDeNoticiasBridge.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?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);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
59
bridges/DiarioDoAlentejoBridge.php
Normal file
59
bridges/DiarioDoAlentejoBridge.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
class DiarioDoAlentejoBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'somini';
|
||||
const NAME = 'Diário do Alentejo';
|
||||
const URI = 'https://www.diariodoalentejo.pt';
|
||||
const DESCRIPTION = 'Semanário Regionalista Independente';
|
||||
const CACHE_TIMEOUT = 28800; // 8h
|
||||
|
||||
/* This is used to hack around obtaining a timestamp. It's just a list of Month names in Portuguese ... */
|
||||
const PT_MONTH_NAMES = array(
|
||||
'janeiro',
|
||||
'fevereiro',
|
||||
'março',
|
||||
'abril',
|
||||
'maio',
|
||||
'junho',
|
||||
'julho',
|
||||
'agosto',
|
||||
'setembro',
|
||||
'outubro',
|
||||
'novembro',
|
||||
'dezembro');
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.diariodoalentejo.pt/images/favicon/apple-touch-icon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
/* This is slow as molasses (>30s!), keep the cache timeout high to avoid killing the host */
|
||||
$html = getSimpleHTMLDOMCached($this->getURI() . '/pt/noticias-listagem.aspx');
|
||||
|
||||
foreach($html->find('.list_news .item') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item_link = $element->find('.body h2.title a', 0);
|
||||
/* Another broken URL, see also `bridges/ComboiosDePortugalBridge.php` */
|
||||
$item['uri'] = self::URI . implode('/', array_map('urlencode', explode('/', $item_link->href)));
|
||||
$item['title'] = $item_link->innertext;
|
||||
|
||||
$item['timestamp'] = str_ireplace(
|
||||
array_map(function($name) { return ' ' . $name . ' '; }, self::PT_MONTH_NAMES),
|
||||
array_map(function($num) { return sprintf('-%02d-', $num); }, range(1, sizeof(self::PT_MONTH_NAMES))),
|
||||
$element->find('span.date', 0)->innertext);
|
||||
|
||||
/* Fix the Image URL */
|
||||
$item_image = $element->find('img.thumb', 0);
|
||||
$item_image->src = preg_replace('/.*&img=([^&]+).*/', '\1', $item_image->getAttribute('data-src'));
|
||||
|
||||
/* Content: */
|
||||
/* - Image */
|
||||
/* - Category */
|
||||
$content = $item_image .
|
||||
'<center>' . $element->find('a.category', 0) . '</center>';
|
||||
$item['content'] = defaultLinkTo($content, self::URI);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -97,8 +97,7 @@ class DiceBridge extends BridgeAbstract {
|
||||
$uri .= '&telecommute=true';
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Dice.');
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
foreach($html->find('div.complete-serp-result-div') as $element) {
|
||||
$item = array();
|
||||
// Title
|
||||
|
@@ -9,8 +9,7 @@ class DilbertBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request Dilbert: ' . self::URI);
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach($html->find('section.comic-item') as $element) {
|
||||
|
||||
|
@@ -44,13 +44,11 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
if(!empty($this->getInput('artistid'))) {
|
||||
$data = getContents('https://api.discogs.com/artists/'
|
||||
. $this->getInput('artistid')
|
||||
. '/releases?sort=year&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/releases?sort=year&sort_order=desc');
|
||||
} elseif(!empty($this->getInput('labelid'))) {
|
||||
$data = getContents('https://api.discogs.com/labels/'
|
||||
. $this->getInput('labelid')
|
||||
. '/releases?sort=year&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/releases?sort=year&sort_order=desc');
|
||||
}
|
||||
|
||||
$jsonData = json_decode($data, true);
|
||||
@@ -76,8 +74,7 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
if(!empty($this->getInput('username_wantlist'))) {
|
||||
$data = getContents('https://api.discogs.com/users/'
|
||||
. $this->getInput('username_wantlist')
|
||||
. '/wants?sort=added&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/wants?sort=added&sort_order=desc');
|
||||
$jsonData = json_decode($data, true)['wants'];
|
||||
|
||||
} elseif(!empty($this->getInput('username_folder'))) {
|
||||
@@ -85,8 +82,7 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
. $this->getInput('username_folder')
|
||||
. '/collection/folders/'
|
||||
. $this->getInput('folderid')
|
||||
. '/releases?sort=added&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/releases?sort=added&sort_order=desc');
|
||||
$jsonData = json_decode($data, true)['releases'];
|
||||
}
|
||||
foreach($jsonData as $element) {
|
||||
|
165
bridges/DockerHubBridge.php
Normal file
165
bridges/DockerHubBridge.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
class DockerHubBridge extends BridgeAbstract {
|
||||
const NAME = 'Docker Hub Bridge';
|
||||
const URI = 'https://hub.docker.com';
|
||||
const DESCRIPTION = 'Returns new images for a container';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'User Submitted Image' => array(
|
||||
'user' => array(
|
||||
'name' => 'User',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rssbridge',
|
||||
),
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rss-bridge',
|
||||
)
|
||||
),
|
||||
'Official Image' => array(
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'postgres',
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
private $apiURL = 'https://hub.docker.com/v2/repositories/';
|
||||
private $imageUrlRegex = '/hub\.docker\.com\/r\/([\w]+)\/([\w-]+)\/?/';
|
||||
private $officialImageUrlRegex = '/hub\.docker\.com\/_\/([\w-]+)\/?/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// user submitted image
|
||||
if(preg_match($this->imageUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'User Submitted Image';
|
||||
$params['user'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
return $params;
|
||||
}
|
||||
|
||||
// official image
|
||||
if(preg_match($this->officialImageUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Official Image';
|
||||
$params['repo'] = $matches[1];
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$json = getContents($this->getApiUrl());
|
||||
|
||||
$data = json_decode($json, false);
|
||||
|
||||
foreach ($data->results as $result) {
|
||||
$item = array();
|
||||
|
||||
$lastPushed = date('Y-m-d H:i:s', strtotime($result->tag_last_pushed));
|
||||
|
||||
$item['title'] = $result->name;
|
||||
$item['uid'] = $result->id;
|
||||
$item['uri'] = $this->getTagUrl($result->name);
|
||||
$item['author'] = $result->last_updater_username;
|
||||
$item['timestamp'] = $result->tag_last_pushed;
|
||||
$item['content'] = <<<EOD
|
||||
<Strong>Tag</strong><br>
|
||||
<p>{$result->name}</p>
|
||||
<Strong>Last pushed</strong><br>
|
||||
<p>{$lastPushed}</p>
|
||||
<Strong>Images</strong><br>
|
||||
{$this->getImages($result)}
|
||||
EOD;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/_/' . $this->getRepo();
|
||||
}
|
||||
|
||||
if ($this->getInput('repo')) {
|
||||
return self::URI . '/r/' . $this->getRepo();
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if ($this->getInput('repo')) {
|
||||
return $this->getRepo() . ' - Docker Hub';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getRepo() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return $this->getInput('repo');
|
||||
}
|
||||
|
||||
return $this->getInput('user') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
private function getApiUrl() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return $this->apiURL . 'library/' . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||
}
|
||||
|
||||
return $this->apiURL . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||
}
|
||||
|
||||
private function getLayerUrl($name, $digest) {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/layers/' . $this->getRepo() . '/library/' .
|
||||
$this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||
}
|
||||
|
||||
return self::URI . '/layers/' . $this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||
}
|
||||
|
||||
private function getTagUrl($name) {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/_/' . $this->getRepo() . '?tab=tags&name=' . $name;
|
||||
}
|
||||
|
||||
return self::URI . '/r/' . $this->getRepo() . '/tags?name=' . $name;
|
||||
}
|
||||
|
||||
private function getImages($result) {
|
||||
$html = <<<EOD
|
||||
<table style="width:300px;"><thead><tr><th>Digest</th><th>OS/architecture</th></tr></thead></tbody>
|
||||
EOD;
|
||||
|
||||
foreach ($result->images as $image) {
|
||||
$layersUrl = $this->getLayerUrl($result->name, $image->digest);
|
||||
$id = $this->getShortDigestId($image->digest);
|
||||
|
||||
$html .= <<<EOD
|
||||
<tr>
|
||||
<td><a href="{$layersUrl}">{$id}</a></td>
|
||||
<td>{$image->os}/{$image->architecture}</td>
|
||||
</tr>
|
||||
EOD;
|
||||
}
|
||||
|
||||
return $html . '</tbody></table>';
|
||||
}
|
||||
|
||||
private function getShortDigestId($digest) {
|
||||
$parts = explode(':', $digest);
|
||||
return substr($parts[1], 0, 12);
|
||||
}
|
||||
}
|
122
bridges/DonnonsBridge.php
Normal file
122
bridges/DonnonsBridge.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* Retourne les dons d'une recherche filtrée sur le site Donnons.org
|
||||
* Example: https://donnons.org/Sport/Ile-de-France
|
||||
*/
|
||||
class DonnonsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Binnette';
|
||||
const NAME = 'Donnons.org';
|
||||
const URI = 'https://donnons.org';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Retourne les dons depuis le site Donnons.org.';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'q' => array(
|
||||
'name' => 'Url de recherche',
|
||||
'required' => true,
|
||||
'exampleValue' => '/Sport/Ile-de-France',
|
||||
'pattern' => '\/.*',
|
||||
'title' => 'Faites une recherche sur le site. Puis copiez ici la fin de l’url. Doit commencer par /',
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Nombre de pages à scanner',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 5,
|
||||
'title' => 'Indique le nombre de pages de donnons.org qui seront scannées'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$pages = $this->getInput('p');
|
||||
|
||||
for($i = 1; $i <= $pages; $i++) {
|
||||
$this->collectDataByPage($i);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectDataByPage($page) {
|
||||
$uri = $this->getPageURI($page);
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
$searchDiv = $html->find('div[id=search]', 0);
|
||||
|
||||
if(!is_null($searchDiv)) {
|
||||
$elements = $searchDiv->find('a.lst-annonce');
|
||||
foreach($elements as $element) {
|
||||
$item = array();
|
||||
|
||||
// Lien vers le don
|
||||
$item['uri'] = self::URI . $element->href;
|
||||
// Id de l'objet
|
||||
$item['uid'] = $element->getAttribute('data-id');
|
||||
|
||||
// Grab info from json
|
||||
$jsonString = $element->find('script', 0)->innertext;
|
||||
$json = json_decode($jsonString, true);
|
||||
|
||||
$name = $json['name'];
|
||||
$category = $json['category'];
|
||||
$date = $json['availabilityStarts'];
|
||||
$description = $json['description'];
|
||||
$city = $json['availableAtOrFrom']['address']['addressLocality'];
|
||||
$region = $json['availableAtOrFrom']['address']['addressRegion'];
|
||||
|
||||
// Grab info from HTML
|
||||
$imageSrc = $element->find('img.ima-center', 0)->getAttribute('data-src');
|
||||
$image = self::URI . $imageSrc;
|
||||
$author = $element->find('div.avatar-holder', 0)->plaintext;
|
||||
|
||||
$content = '
|
||||
<img style="margin-right:1em;" src="' . $image . '">
|
||||
<div>
|
||||
<h1>' . $name . '</h1>
|
||||
<p>' . $description . '</p>
|
||||
<p>Lieu : <b>' . $city . '</b> - ' . $region . '</p>
|
||||
<p>Par : ' . $author . '</p>
|
||||
<p>Date : ' . $date . '</p>
|
||||
</div>
|
||||
';
|
||||
|
||||
// Titre du don
|
||||
$item['title'] = '[' . $category . '] ' . $name;
|
||||
$item['timestamp'] = $date;
|
||||
$item['author'] = $author;
|
||||
$item['content'] = $content;
|
||||
$item['enclosures'] = array($image);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getPageURI($page) {
|
||||
$uri = $this->getURI();
|
||||
$haveQueryParams = strpos($uri, '?') !== false;
|
||||
|
||||
if($haveQueryParams) {
|
||||
return $uri . '&page=' . $page;
|
||||
} else {
|
||||
return $uri . '?page=' . $page;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return self::URI . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return 'Donnons.org - ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
194
bridges/DownDetectorBridge.php
Normal file
194
bridges/DownDetectorBridge.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
class DownDetectorBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'DownDetector Bridge';
|
||||
const URI = 'https://downdetector.com/';
|
||||
const DESCRIPTION = 'Returns most recent downtimes from DownDetector';
|
||||
const CACHE_TIMEOUT = 300; // 5 min
|
||||
|
||||
const PARAMETERS = array(
|
||||
'All Websites' => array(
|
||||
'country' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Country',
|
||||
'values' => array(
|
||||
'Argentina' => 'https://downdetector.com.ar',
|
||||
'Australia' => 'https://downdetector.com.au',
|
||||
'België' => 'https://allestoringen.be',
|
||||
'Brasil' => 'https://downdetector.com.br',
|
||||
'Canada' => 'https://downdetector.ca',
|
||||
'Chile' => 'https://downdetector.cl',
|
||||
'Colombia' => 'https://downdetector.com.co',
|
||||
'Danmark' => 'https://downdetector.dk',
|
||||
'Deutschland' => 'https://allestörungen.de',
|
||||
'Ecuador' => 'https://downdetector.ec',
|
||||
'España' => 'https://downdetector.es',
|
||||
'France' => 'https://downdetector.fr',
|
||||
'Hong Kong' => 'https://downdetector.hk',
|
||||
'Hrvatska' => 'https://downdetector.hr',
|
||||
'India' => 'https://downdetector.in',
|
||||
'Indonesia' => 'https://downdetector.id',
|
||||
'Ireland' => 'https://downdetector.ie',
|
||||
'Italia' => 'https://downdetector.it',
|
||||
'Magyarország' => 'https://downdetector.hu',
|
||||
'Malaysia' => 'https://downdetector.my',
|
||||
'México' => 'https://downdetector.mx',
|
||||
'Nederland' => 'https://allestoringen.nl',
|
||||
'New Zealand' => 'https://downdetector.co.nz',
|
||||
'Norge' => 'https://downdetector.no',
|
||||
'Pakistan' => 'https://downdetector.pk',
|
||||
'Perú' => 'https://downdetector.pe',
|
||||
'Pilipinas' => 'https://downdetector.ph',
|
||||
'Polska' => 'https://downdetector.pl',
|
||||
'Portugal' => 'https://downdetector.pt',
|
||||
'România' => 'https://downdetector.ro',
|
||||
'Schweiz' => 'https://allestörungen.ch',
|
||||
'Singapore' => 'https://downdetector.sg',
|
||||
'Slovensko' => 'https://downdetector.sk',
|
||||
'South Africa' => 'https://downdetector.co.za',
|
||||
'Suomi' => 'https://downdetector.fi',
|
||||
'Sverige' => 'https://downdetector.se',
|
||||
'Türkiye' => 'https://downdetector.web.tr',
|
||||
'UAE' => 'https://downdetector.ae',
|
||||
'UK' => 'https://downdetector.co.uk',
|
||||
'United States' => 'https://downdetector.com',
|
||||
'Österreich' => 'https://allestörungen.at',
|
||||
'Česko' => 'https://downdetector.cz',
|
||||
'Ελλάς' => 'https://downdetector.gr',
|
||||
'Россия' => 'https://downdetector.ru',
|
||||
'日本' => 'https://downdetector.jp'
|
||||
)
|
||||
)
|
||||
),
|
||||
'Specific Website' => array(
|
||||
'page' => array(
|
||||
'type' => 'text',
|
||||
'name' => 'Status page',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://downdetector.com/status/rainbow-six',
|
||||
'title' => 'URL of a DownDetector status page e.g: https://downdetector.com/status/rainbow-six/',
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
private $hostname = '';
|
||||
private $statusPageId = '';
|
||||
private $feedname = '';
|
||||
|
||||
private $statusUrlRegex = '/\/([a-zA-z0-9ö.]+)\/(?:statu(?:s|t)|problemas?|nu-merge
|
||||
|(?:feil-)?problem(y|i)?(?:-storningar)?(?:-fejl)?|stoerung|durum|storing|fora-do-ar|ne-rabotaet
|
||||
|masalah|shougai|ei-toimi)\/([a-zA-Z0-9-]+)/';
|
||||
|
||||
public function collectData(){
|
||||
if ($this->queriedContext == 'Specific Website') {
|
||||
preg_match($this->statusUrlRegex, $this->getInput('page'), $match)
|
||||
or returnClientError('Given URL does not seem to at a DownDetector status page!');
|
||||
|
||||
$this->hostname = $match[1];
|
||||
$this->statusPageId = $match[3];
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/archive/')
|
||||
or returnClientError('Could not request website!.');
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
$this->feedname = $html->find('li.breadcrumb-item.active', 0)->plaintext;
|
||||
}
|
||||
|
||||
$table = $html->find('table.table-striped', 0);
|
||||
|
||||
if ($table) {
|
||||
foreach ($table->find('tr') as $event) {
|
||||
$td = $event->find('td', 0);
|
||||
|
||||
if (is_null($td)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['uri'] = $event->find('td', 0)->find('a', 0)->href;
|
||||
$item['title'] = $event->find('td', 0)->find('a', 0)->plaintext .
|
||||
'(' . trim($event->find('td', 1)->plaintext) . ' ' . trim($event->find('td', 2)->plaintext) . ')';
|
||||
$item['content'] = 'User reports indicate problems at' . $event->find('td', 0)->find('a', 0)->plaintext .
|
||||
' since ' . $event->find('td', 2)->plaintext;
|
||||
$item['timestamp'] = $this->formatDate(
|
||||
trim($event->find('td', 1)->plaintext),
|
||||
trim($event->find('td', 2)->plaintext)
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if($this->getInput('country')) {
|
||||
return $this->getInput('country');
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
return 'https://' . $this->hostname . '/status/' . $this->statusPageId;
|
||||
}
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if($this->getInput('country')) {
|
||||
$country = $this->getCountry($this->getInput('country'));
|
||||
return $country . ' - DownDetector';
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
$country = $this->getCountry($this->hostname);
|
||||
return $this->feedname . ' - ' . $country . ' - DownDetector';
|
||||
}
|
||||
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
private function formatDate($date, $time) {
|
||||
switch($this->getCountry()) {
|
||||
case 'Australia':
|
||||
case 'UK':
|
||||
$date = DateTime::createFromFormat('d/m/Y', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
case 'Brasil':
|
||||
case 'Chile':
|
||||
case 'Colombia':
|
||||
case 'Ecuador':
|
||||
case 'España':
|
||||
case 'Italia':
|
||||
case 'Perú':
|
||||
case 'Portugal':
|
||||
$date = DateTime::createFromFormat('d/m/Y', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
case 'Magyarország':
|
||||
$date = DateTime::createFromFormat('Y.m.d.', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
default:
|
||||
return $date . $time;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCountry() {
|
||||
if($this->getInput('country')) {
|
||||
$input = $this->getInput('country');
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
if (empty($this->hostname)) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
$input = 'https://' . $this->hostname;
|
||||
}
|
||||
|
||||
$parameters = $this->getParameters();
|
||||
$countryValues = array_flip($parameters['All Websites']['country']['values']);
|
||||
$country = $countryValues[$input];
|
||||
|
||||
return $country;
|
||||
}
|
||||
}
|
@@ -13,48 +13,50 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$json = $this->loadEmbeddedJsonData($html);
|
||||
|
||||
foreach($html->find('li[id^="screenshot-"]') as $shot) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$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'] = [$this->getFullSizeImagePath($preview_path)];
|
||||
$item['enclosures'] = array($this->getFullSizeImagePath($preview_path));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function loadEmbeddedJsonData($html){
|
||||
$json = [];
|
||||
$json = array();
|
||||
$scripts = $html->find('script');
|
||||
|
||||
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 +85,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 +93,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);
|
||||
}
|
||||
}
|
||||
|
205
bridges/Drive2ruBridge.php
Normal file
205
bridges/Drive2ruBridge.php
Normal file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
class Drive2ruBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'dotter-ak';
|
||||
const NAME = 'Drive2.ru';
|
||||
const URI = 'https://drive2.ru/';
|
||||
const DESCRIPTION = 'Лента новостей и тестдрайвов, бортжурналов по выбранной марке или модели
|
||||
(также работает с фильтром по категориям), блогов пользователей и публикаций по темам.';
|
||||
const PARAMETERS = array(
|
||||
'Новости и тест-драйвы' => array(),
|
||||
'Бортжурналы (По модели или марке)' => array(
|
||||
'url' => array(
|
||||
'name' => 'Ссылка на страницу с бортжурналом',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Например: https://www.drive2.ru/experience/suzuki/g4895/',
|
||||
'exampleValue' => 'https://www.drive2.ru/experience/suzuki/g4895/'
|
||||
),
|
||||
),
|
||||
'Личные блоги' => array(
|
||||
'username' => array(
|
||||
'name' => 'Никнейм пользователя на сайте',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Например: Mickey',
|
||||
'exampleValue' => 'Mickey'
|
||||
)
|
||||
),
|
||||
'Публикации по темам (Стоит почитать)' => array(
|
||||
'topic' => array(
|
||||
'name' => 'Темы',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Автозвук' => '16',
|
||||
'Автомобильный дизайн' => '10',
|
||||
'Автоспорт' => '11',
|
||||
'Автошоу, музеи, выставки' => '12',
|
||||
'Безопасность' => '18',
|
||||
'Беспилотные автомобили' => '15',
|
||||
'Видеосюжеты' => '20',
|
||||
'Вне дорог' => '21',
|
||||
'Встречи' => '22',
|
||||
'Выбор и покупка машины' => '23',
|
||||
'Гаджеты' => '30',
|
||||
'Гибридные машины' => '32',
|
||||
'Грузовики, автобусы, спецтехника' => '31',
|
||||
'Доработка интерьера' => '35',
|
||||
'Законодательство' => '40',
|
||||
'История автомобилестроения' => '50',
|
||||
'Мототехника' => '60',
|
||||
'Новые модели и концепты' => '85',
|
||||
'Обучение вождению' => '70',
|
||||
'Путешествия' => '80',
|
||||
'Ремонт и обслуживание' => '90',
|
||||
'Реставрация ретро-авто' => '91',
|
||||
'Сделай сам' => '104',
|
||||
'Смешное' => '103',
|
||||
'Спорткары' => '102',
|
||||
'Стайлинг' => '101',
|
||||
'Тест-драйвы' => '110',
|
||||
'Тюнинг' => '111',
|
||||
'Фотосессии' => '120',
|
||||
'Шины и диски' => '140',
|
||||
'Электрика' => '130',
|
||||
'Электромобили' => '131'
|
||||
),
|
||||
'defaultValue' => '16',
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'full_articles' => array(
|
||||
'name' => 'Загружать в ленту полный текст',
|
||||
'type' => 'checkbox'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $title;
|
||||
|
||||
private function getUserContent($url) {
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
$articles = $html->find('div.js-entity');
|
||||
foreach ($articles as $article) {
|
||||
$item = array();
|
||||
$item['title'] = $article->find('a.c-link--text', 0)->plaintext;
|
||||
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 0)->href);
|
||||
if($this->getInput('full_articles')) {
|
||||
$item['content'] = $this->addCommentsLink(
|
||||
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.c-post__body', 0))->innertext,
|
||||
$item['uri']
|
||||
);
|
||||
} else {
|
||||
$item['content'] = $this->addReadMoreLink($article->find('div.c-post-preview__lead', 0), $item['uri']);
|
||||
}
|
||||
$item['author'] = $article->find('a.c-username--wrap', 0)->plaintext;
|
||||
if (!is_null($article->find('img', 1))) $item['enclosures'][] = $article->find('img', 1)->src;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getLogbooksContent($url) {
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
$articles = $html->find('div.js-entity');
|
||||
foreach ($articles as $article) {
|
||||
$item = array();
|
||||
$item['title'] = $article->find('a.c-link--text', 1)->plaintext;
|
||||
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 1)->href);
|
||||
if($this->getInput('full_articles')) {
|
||||
$item['content'] = $this->addCommentsLink(
|
||||
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.c-post__body', 0))->innertext,
|
||||
$item['uri']
|
||||
);
|
||||
} else {
|
||||
$item['content'] = $this->addReadMoreLink($article->find('div.c-post-preview__lead', 0), $item['uri']);
|
||||
}
|
||||
$item['author'] = $article->find('a.c-username--wrap', 0)->plaintext;
|
||||
if (!is_null($article->find('img', 1))) $item['enclosures'][] = $article->find('img', 1)->src;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getNews() {
|
||||
$html = getSimpleHTMLDOM('https://www.drive2.ru/editorial/');
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
$articles = $html->find('div.c-article-card');
|
||||
foreach ($articles as $article) {
|
||||
$item = array();
|
||||
$item['title'] = $article->find('a.c-link--text', 0)->plaintext;
|
||||
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 0)->href);
|
||||
if($this->getInput('full_articles')) {
|
||||
$item['content'] = $this->addCommentsLink(
|
||||
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.article', 0))->innertext,
|
||||
$item['uri']
|
||||
);
|
||||
} else {
|
||||
$item['content'] = $this->addReadMoreLink($article->find('div.c-article-card__lead', 0), $item['uri']);
|
||||
}
|
||||
$item['author'] = 'Новости и тест-драйвы на Drive2.ru';
|
||||
if (!is_null($article->find('img', 0))) $item['enclosures'][] = $article->find('img', 0)->src;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function adjustContent($content) {
|
||||
foreach ($content->find('div.o-group') as $node)
|
||||
$node->outertext = '';
|
||||
foreach($content->find('div, span') as $attrs)
|
||||
foreach ($attrs->getAllAttributes() as $attr => $val)
|
||||
$attrs->removeAttribute($attr);
|
||||
foreach ($content->getElementsByTagName('figcaption') as $attrs)
|
||||
$attrs->setAttribute(
|
||||
'style',
|
||||
'font-style: italic; font-size: small; margin: 0 100px 75px;');
|
||||
foreach ($content->find('script') as $node)
|
||||
$node->outertext = '';
|
||||
foreach ($content->find('iframe') as $node) {
|
||||
preg_match('/embed\/(.*?)\?/', $node->src, $match);
|
||||
$node->outertext = '<a href="https://www.youtube.com/watch?v=' . $match[1] .
|
||||
'">https://www.youtube.com/watch?v=' . $match[1] . '</a>';
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function addCommentsLink ($content, $url) {
|
||||
return $content . '<br><a href="' . $url . '#comments">Перейти к комментариям</a>';
|
||||
}
|
||||
|
||||
private function addReadMoreLink ($content, $url) {
|
||||
if (!is_null($content))
|
||||
return preg_replace('!\s+!', ' ', str_replace('Читать дальше', '', $content->plaintext)) .
|
||||
'<br><a href="' . $url . '">Читать далее</a>';
|
||||
else return '';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
default:
|
||||
case 'Новости и тест-драйвы':
|
||||
$this->getNews();
|
||||
break;
|
||||
case 'Бортжурналы (По модели или марке)':
|
||||
if (!preg_match('/^https:\/\/www.drive2.ru\/experience/', $this->getInput('url')))
|
||||
returnServerError('Invalid url');
|
||||
$this->getLogbooksContent($this->getInput('url'));
|
||||
break;
|
||||
case 'Личные блоги':
|
||||
if (!preg_match('/^[a-zA-Z0-9-]{3,16}$/', $this->getInput('username')))
|
||||
returnServerError('Invalid username');
|
||||
$this->getUserContent('https://www.drive2.ru/users/' . $this->getInput('username'));
|
||||
break;
|
||||
case 'Публикации по темам (Стоит почитать)':
|
||||
$this->getUserContent('https://www.drive2.ru/topics/' . $this->getInput('topic'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.drive2.ru/favicon.ico';
|
||||
}
|
||||
}
|
@@ -28,14 +28,13 @@ class DuckDuckGoBridge extends BridgeAbstract {
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'html/?kd=-1&q=' . $this->getInput('u') . $this->getInput('sort'))
|
||||
or returnServerError('Could not request DuckDuckGo.');
|
||||
$html = getSimpleHTMLDOM(self::URI . 'html/?kd=-1&q=' . $this->getInput('u') . $this->getInput('sort'));
|
||||
|
||||
foreach($html->find('div.results_links') as $element) {
|
||||
foreach($html->find('div.result') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['title'] = $element->find('a', 1)->innertext;
|
||||
$item['content'] = $element->find('div.snippet', 0)->plaintext;
|
||||
$item['uri'] = $element->find('a.result__a', 0)->href;
|
||||
$item['title'] = $element->find('h2.result__title', 0)->plaintext;
|
||||
$item['content'] = $element->find('a.result__snippet', 0)->plaintext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -107,8 +107,7 @@ class ETTVBridge extends BridgeAbstract {
|
||||
|
||||
// Get results page
|
||||
$this->results_link = self::URI . $query_str;
|
||||
$html = getSimpleHTMLDOM($this->results_link)
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
$html = getSimpleHTMLDOM($this->results_link);
|
||||
|
||||
// Loop on each entry
|
||||
foreach($html->find('table.table tr') as $element) {
|
||||
@@ -117,8 +116,7 @@ class ETTVBridge extends BridgeAbstract {
|
||||
|
||||
// retrieve result page to get more details
|
||||
$link = rtrim(self::URI, '/') . $entry->href;
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request page ' . $link);
|
||||
$page = getSimpleHTMLDOM($link);
|
||||
|
||||
// get details & download links
|
||||
$details = $page->find('fieldset.download table', 0); // WHAT?? It should be the second one…
|
||||
|
@@ -35,8 +35,7 @@ on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.';
|
||||
foreach($showList as $showID) {
|
||||
|
||||
// Get show page
|
||||
$html = getSimpleHTMLDOM(self::URI . 'shows/' . rawurlencode($showID) . '/')
|
||||
or returnServerError('Could not request EZTV for id "' . $showID . '"');
|
||||
$html = getSimpleHTMLDOM(self::URI . 'shows/' . rawurlencode($showID) . '/');
|
||||
|
||||
// Loop on each element that look like an episode entry...
|
||||
foreach($html->find('.forum_header_border') as $element) {
|
||||
|
@@ -1,63 +1,128 @@
|
||||
<?php
|
||||
class EconomistBridge extends BridgeAbstract {
|
||||
const NAME = 'The Economist: Latest Updates';
|
||||
const URI = 'https://www.economist.com';
|
||||
const DESCRIPTION = 'Fetches the latest updates from the Economist.';
|
||||
const MAINTAINER = 'thefranke';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
class EconomistBridge extends FeedExpander {
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.economist.com/sites/default/files/econfinal_favicon.ico';
|
||||
const MAINTAINER = 'bockiii';
|
||||
const NAME = 'Economist Bridge';
|
||||
const URI = 'https://www.economist.com/';
|
||||
const CACHE_TIMEOUT = 3600; //1hour
|
||||
const DESCRIPTION = 'Returns the latest articles for the selected category';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'global' => array(
|
||||
'limit' => array(
|
||||
'name' => 'Feed Item Limit',
|
||||
'required' => true,
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Maximum number of returned feed items. Maximum 30, default 10'
|
||||
)
|
||||
),
|
||||
'Topics' => array(
|
||||
'topic' => array(
|
||||
'name' => 'Topics',
|
||||
'type' => 'list',
|
||||
'title' => 'Select a Topic',
|
||||
'defaultValue' => 'latest',
|
||||
'values' => array(
|
||||
'Latest' => 'latest',
|
||||
'The world this week' => 'the-world-this-week',
|
||||
'Letters' => 'letters',
|
||||
'Leaders' => 'leaders',
|
||||
'Briefings' => 'briefing',
|
||||
'Special reports' => 'special-report',
|
||||
'Britain' => 'britain',
|
||||
'Europe' => 'europe',
|
||||
'United States' => 'united-states',
|
||||
'The Americas' => 'the-americas',
|
||||
'Middle East and Africa' => 'middle-east-and-africa',
|
||||
'Asia' => 'asia',
|
||||
'China' => 'china',
|
||||
'International' => 'international',
|
||||
'Business' => 'business',
|
||||
'Finance and economics' => 'finance-and-economics',
|
||||
'Science and technology' => 'science-and-technology',
|
||||
'Books and arts' => 'books-and-arts',
|
||||
'Obituaries' => 'obituary',
|
||||
'Graphic detail' => 'graphic-detail',
|
||||
'Indicators' => 'economic-and-financial-indicators',
|
||||
)
|
||||
)
|
||||
),
|
||||
'Blogs' => array(
|
||||
'blog' => array(
|
||||
'name' => 'Blogs',
|
||||
'type' => 'list',
|
||||
'title' => 'Select a Blog',
|
||||
'values' => array(
|
||||
'Bagehots notebook' => 'bagehots-notebook',
|
||||
'Bartleby' => 'bartleby',
|
||||
'Buttonwoods notebook' => 'buttonwoods-notebook',
|
||||
'Charlemagnes notebook' => 'charlemagnes-notebook',
|
||||
'Democracy in America' => 'democracy-in-america',
|
||||
'Erasmus' => 'erasmus',
|
||||
'Free exchange' => 'free-exchange',
|
||||
'Game theory' => 'game-theory',
|
||||
'Gulliver' => 'gulliver',
|
||||
'Kaffeeklatsch' => 'kaffeeklatsch',
|
||||
'Prospero' => 'prospero',
|
||||
'The Economist Explains' => 'the-economist-explains',
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
// get if topics or blogs were selected and store the selected category
|
||||
switch ($this->queriedContext) {
|
||||
case 'Topics':
|
||||
$category = $this->getInput('topic');
|
||||
break;
|
||||
case 'Blogs':
|
||||
$category = $this->getInput('blog');
|
||||
break;
|
||||
default:
|
||||
$category = 'latest';
|
||||
}
|
||||
// limit the returned articles to 30 at max
|
||||
if ((int)$this->getInput('limit') <= 30) {
|
||||
$limit = (int)$this->getInput('limit');
|
||||
} else {
|
||||
$limit = 30;
|
||||
}
|
||||
|
||||
$this->collectExpandableDatas('https://www.economist.com/' . $category . '/rss.xml', $limit);
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI . '/latest/')
|
||||
or returnServerError('Could not fetch latest updates form The Economist.');
|
||||
protected function parseItem($feedItem){
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
foreach($html->find('article') as $element) {
|
||||
|
||||
$a = $element->find('a', 0);
|
||||
$href = self::URI . $a->href;
|
||||
$full = getSimpleHTMLDOMCached($href);
|
||||
$article = $full->find('article', 0);
|
||||
|
||||
$header = $article->find('h1', 0);
|
||||
$author = $article->find('span[itemprop="author"]', 0);
|
||||
$time = $article->find('time[itemprop="dateCreated"]', 0);
|
||||
$content = $article->find('div[itemprop="description"]', 0);
|
||||
|
||||
// Remove newsletter subscription box
|
||||
$newsletter = $content->find('div[class="newsletter-form__message"]', 0);
|
||||
if ($newsletter)
|
||||
$newsletter->outertext = '';
|
||||
|
||||
$newsletterForm = $content->find('form', 0);
|
||||
if ($newsletterForm)
|
||||
$newsletterForm->outertext = '';
|
||||
|
||||
// Remove next and previous article URLs at the bottom
|
||||
$nextprev = $content->find('div[class="blog-post__next-previous-wrapper"]', 0);
|
||||
if ($nextprev)
|
||||
$nextprev->outertext = '';
|
||||
|
||||
$section = [ $article->find('h3[itemprop="articleSection"]', 0)->plaintext ];
|
||||
|
||||
$item = array();
|
||||
$item['title'] = $header->find('span', 0)->innertext . ': '
|
||||
. $header->find('span', 1)->innertext;
|
||||
|
||||
$item['uri'] = $href;
|
||||
$item['timestamp'] = strtotime($time->datetime);
|
||||
$item['author'] = $author->innertext;
|
||||
$item['categories'] = $section;
|
||||
|
||||
$item['content'] = '<img style="max-width: 100%" src="'
|
||||
. $a->find('img', 0)->src . '">' . $content->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
$article = getSimpleHTMLDOM($item['uri']);
|
||||
// before the article can be added, it needs to be cleaned up, thus, the extra function
|
||||
$item['content'] = $this->cleanContent($article);
|
||||
// only the article lead image is retained if it's there
|
||||
if (!is_null($article->find('div.article__lead-image', 0))) {
|
||||
$item['enclosures'][] = $article->find('div.article__lead-image', 0)->find('img', 0)->getAttribute('src');
|
||||
} else {
|
||||
$item['enclosures'][] = '';
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function cleanContent($article){
|
||||
// the actual article is in this div
|
||||
$content = $article->find('div.layout-article-body', 0)->innertext;
|
||||
// clean the article content. Remove all div's since the text is in paragraph elements
|
||||
foreach (array(
|
||||
'<div '
|
||||
) as $tag_start) {
|
||||
$content = stripRecursiveHTMLSection($content, 'div', $tag_start);
|
||||
}
|
||||
// now remove embedded iframes. The podcast postings contain these for example
|
||||
$content = preg_replace('/<iframe.*?\/iframe>/i', '', $content);
|
||||
// fix the relative links
|
||||
$content = defaultLinkTo($content, $this->getURI());
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
|
@@ -25,8 +25,7 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
|
||||
$language = $this->getInput('language');
|
||||
$url = 'https://community.elitedangerous.com/';
|
||||
$url = $url . $language . '/galnet';
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
foreach($html->find('div.article') as $element) {
|
||||
$item = array();
|
||||
|
@@ -95,7 +95,7 @@ class ElloBridge extends BridgeAbstract {
|
||||
|
||||
private function getEnclosures($post, $postData) {
|
||||
|
||||
$assets = [];
|
||||
$assets = array();
|
||||
foreach($post->links->assets as $asset) {
|
||||
foreach($postData->linked->assets as $assetLink) {
|
||||
if($asset == $assetLink->id) {
|
||||
@@ -124,7 +124,7 @@ class ElloBridge extends BridgeAbstract {
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope(get_called_class());
|
||||
$cache->setKey(['key']);
|
||||
$cache->setKey(array('key'));
|
||||
$key = $cache->loadData();
|
||||
|
||||
if($key == null) {
|
||||
|
@@ -3,7 +3,7 @@ class ElsevierBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Pierre Mazière';
|
||||
const NAME = 'Elsevier journals recent articles';
|
||||
const URI = 'http://www.journals.elsevier.com/';
|
||||
const URI = 'https://www.journals.elsevier.com/';
|
||||
const CACHE_TIMEOUT = 43200; //12h
|
||||
const DESCRIPTION = 'Returns the recent articles published in Elsevier journals';
|
||||
|
||||
@@ -63,8 +63,7 @@ class ElsevierBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('No results for Elsevier journal ' . $this->getInput('j'));
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
foreach($html->find('.pod-listing') as $article) {
|
||||
$item = array();
|
||||
|
91
bridges/EpicgamesBridge.php
Normal file
91
bridges/EpicgamesBridge.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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);
|
||||
$dataBlog = getContents($urlBlog);
|
||||
|
||||
// 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'));
|
||||
}
|
||||
}
|
69
bridges/EsquerdaNetBridge.php
Normal file
69
bridges/EsquerdaNetBridge.php
Normal file
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
class EsquerdaNetBridge extends FeedExpander {
|
||||
const MAINTAINER = 'somini';
|
||||
const NAME = 'Esquerda.net';
|
||||
const URI = 'https://www.esquerda.net';
|
||||
const DESCRIPTION = 'Esquerda.net';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'feed' => array(
|
||||
'name' => 'Feed',
|
||||
'type' => 'list',
|
||||
'defaultValue' => 'Geral',
|
||||
'values' => array(
|
||||
'Geral' => 'geral',
|
||||
'Dossier' => 'artigos-dossier',
|
||||
'Vídeo' => 'video',
|
||||
'Opinião' => 'opinioes',
|
||||
'Rádio' => 'radio',
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getURI() {
|
||||
$type = $this->getInput('feed');
|
||||
return self::URI . '/rss/' . $type;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.esquerda.net/sites/default/files/favicon_0.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
parent::collectExpandableDatas($this->getURI());
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
# Fix Publish date
|
||||
$badDate = $newsItem->pubDate;
|
||||
preg_match('|(?P<day>\d\d)/(?P<month>\d\d)/(?P<year>\d\d\d\d) - (?P<hour>\d\d):(?P<minute>\d\d)|', $badDate, $d);
|
||||
$newsItem->pubDate = sprintf('%s-%s-%sT%s:%s', $d['year'], $d['month'], $d['day'], $d['hour'], $d['minute']);
|
||||
$item = parent::parseItem($newsItem);
|
||||
# Include all the content
|
||||
$uri = $item['uri'];
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
$content = $html->find('div#content div.content', 0);
|
||||
## Fix author
|
||||
$authorHTML = $html->find('.field-name-field-op-author a', 0);
|
||||
if ($authorHTML) {
|
||||
$item['author'] = $authorHTML->innertext;
|
||||
$authorHTML->remove();
|
||||
}
|
||||
## Remove crap
|
||||
$content->find('.field-name-addtoany', 0)->remove();
|
||||
## Fix links
|
||||
$content = defaultLinkTo($content, self::URI);
|
||||
## Fix Images
|
||||
foreach($content->find('img') as $img) {
|
||||
$altSrc = $img->getAttribute('data-src');
|
||||
if ($altSrc) {
|
||||
$img->setAttribute('src', $altSrc);
|
||||
}
|
||||
$img->width = null;
|
||||
$img->height = null;
|
||||
}
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -8,8 +8,7 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'Should we put a website in production today? (French)';
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request EstCeQuonMetEnProd: ' . self::URI);
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '#' . date('Y-m-d');
|
||||
|
@@ -33,8 +33,7 @@ class EtsyBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$results = $html->find('li.block-grid-item');
|
||||
|
||||
|
59
bridges/ExplosmBridge.php
Normal file
59
bridges/ExplosmBridge.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
class ExplosmBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'bockiii';
|
||||
const NAME = 'Explosm Bridge';
|
||||
const URI = 'https://www.explosm.net/';
|
||||
const CACHE_TIMEOUT = 4800; //2hours
|
||||
const DESCRIPTION = 'Returns the last 5 comics';
|
||||
const PARAMETERS = array(
|
||||
'Get latest posts' => array(
|
||||
'limit' => array(
|
||||
'name' => 'Posts limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 5
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$limit = $this->getInput('limit');
|
||||
$latest = getSimpleHTMLDOM('https://explosm.net/comics/latest');
|
||||
$image = $latest->find('div[id=comic]', 0)->find('img', 0)->getAttribute('src');
|
||||
$date_string = $latest->find('p[class*=Author__P]', 0)->innertext;
|
||||
$next_data_string = $latest->find('script[id=__NEXT_DATA__]', 0)->innertext;
|
||||
$exp = '/{\\\"latest\\\":\[{\\\"slug\\\":\\\"(.*?)\\ /';
|
||||
$reg_array = array();
|
||||
preg_match($exp, $next_data_string, $reg_array);
|
||||
$comic_id = $reg_array[1];
|
||||
$comic_id = substr($comic_id, 0, strpos($comic_id, '\\'));
|
||||
$item = array();
|
||||
$item['uri'] = $this::URI . 'comics/' . $comic_id;
|
||||
$item['uid'] = $this::URI . 'comics/' . $comic_id;
|
||||
$item['title'] = 'Comic for ' . $date_string;
|
||||
$item['timestamp'] = strtotime($date_string);
|
||||
$item['author'] = $latest->find('p[class*=Author__P]', 2)->innertext;
|
||||
$item['content'] = '<img src="' . $image . '" />';
|
||||
$this->items[] = $item;
|
||||
|
||||
$next_comic = substr($this::URI, 0, -1)
|
||||
. $latest->find('div[class*=MainComic__Selector]', 0)->find('a', 0)->getAttribute('href');
|
||||
// use index 1 as the latest comic was already found
|
||||
for ($i = 1; $i <= $limit; $i++) {
|
||||
$this_comic = getSimpleHTMLDOM($next_comic);
|
||||
$image = $this_comic->find('div[id=comic]', 0)->find('img', 0)->getAttribute('src');
|
||||
$date_string = $this_comic->find('p[class*=Author__P]', 0)->innertext;
|
||||
$item = array();
|
||||
$item['uri'] = $next_comic;
|
||||
$item['uid'] = $next_comic;
|
||||
$item['title'] = 'Comic for ' . $date_string;
|
||||
$item['timestamp'] = strtotime($date_string);
|
||||
$item['author'] = $this_comic->find('p[class*=Author__P]', 2)->innertext;
|
||||
$item['content'] = '<img src="' . $image . '" />';
|
||||
$this->items[] = $item;
|
||||
$next_comic = substr($this::URI, 0, -1)
|
||||
. $this_comic->find('div[class*=MainComic__Selector]', 0)->find('a', 0)->getAttribute('href'); // get next comic link
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class ExtremeDownloadBridge extends BridgeAbstract {
|
||||
const NAME = 'Extreme Download';
|
||||
const URI = 'https://ww1.extreme-d0wn.com/';
|
||||
const URI = 'https://www.extreme-down.plus/';
|
||||
const DESCRIPTION = 'Suivi de série sur Extreme Download';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
@@ -10,7 +10,7 @@ class ExtremeDownloadBridge extends BridgeAbstract {
|
||||
'name' => 'URL de la série',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une série sans le https://ww1.extreme-d0wn.com/',
|
||||
'title' => 'URL d\'une série sans le https://www.extreme-down.plus/',
|
||||
'exampleValue' => 'series-hd/hd-series-vostfr/46631-halt-and-catch-fire-saison-04-vostfr-hdtv-720p.html'),
|
||||
'filter' => array(
|
||||
'name' => 'Type de contenu',
|
||||
@@ -26,8 +26,7 @@ class ExtremeDownloadBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
or returnServerError('Could not request Extreme Download.');
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'));
|
||||
|
||||
$filter = $this->getInput('filter');
|
||||
|
||||
@@ -81,6 +80,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 = '';
|
||||
|
@@ -2,7 +2,7 @@
|
||||
class FB2Bridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Facebook Alternate';
|
||||
const NAME = 'Facebook Bridge | Touch Site';
|
||||
const URI = 'https://www.facebook.com/';
|
||||
const CACHE_TIMEOUT = 1000;
|
||||
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
|
||||
@@ -12,7 +12,12 @@ class FB2Bridge extends BridgeAbstract {
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'abbrev_name' => array(
|
||||
'name' => 'Abbreviate author name in title',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => true,
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
@@ -102,12 +107,12 @@ EOD
|
||||
else
|
||||
$timestamp = 0;
|
||||
|
||||
$item['uri'] = html_entity_decode('http://touch.facebook.com'
|
||||
$item['uri'] = html_entity_decode('https://touch.facebook.com'
|
||||
. $content->find("div[class='_52jc _5qc4 _78cz _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
|
||||
|
||||
//Decode images
|
||||
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
|
||||
return "<img src='" . str_replace(['\\3a ', '\\3d ', '\\26 '], [':', '=', '&'], $matches[1]) . "' />";
|
||||
return "<img src='" . str_replace(array('\\3a ', '\\3d ', '\\26 '), array(':', '=', '&'), $matches[1]) . "' />";
|
||||
}, $content);
|
||||
$content = str_get_html($imagecleaned);
|
||||
|
||||
@@ -159,7 +164,11 @@ EOD
|
||||
$content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content);
|
||||
|
||||
//Remove the double section tags
|
||||
$content = str_replace(['<section><section>', '</section></section>'], ['<section>', '</section>'], $content);
|
||||
$content = str_replace(
|
||||
array('<section><section>', '</section></section>'),
|
||||
array('<section>', '</section>'),
|
||||
$content
|
||||
);
|
||||
|
||||
//Move the section tag link upper, if it is down
|
||||
$content = str_get_html($content);
|
||||
@@ -182,8 +191,10 @@ EOD
|
||||
$item['content'] = html_entity_decode($content, ENT_QUOTES);
|
||||
|
||||
$title = $author;
|
||||
if (strlen($title) > 24)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
|
||||
if ($this->getInput('abbrev_name') === true) {
|
||||
if (strlen($title) > 24)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
|
||||
}
|
||||
$title = $title . ' | ' . strip_tags($content);
|
||||
if (strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
@@ -281,10 +292,20 @@ EOD
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
return (isset($this->name) ? $this->name . ' - ' : '') . 'Facebook Bridge';
|
||||
$username = $this->getInput('u');
|
||||
if (isset($username)) {
|
||||
return $this->getInput('u') . ' | Facebook';
|
||||
} else {
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
return 'http://facebook.com';
|
||||
$username = $this->getInput('u');
|
||||
if (isset($username)) {
|
||||
return 'https://facebook.com/' . $this->getInput('u') . '/posts';
|
||||
} else {
|
||||
return self::URI;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,8 +24,7 @@ class FDroidBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI;
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request F-Droid.');
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
// targetting the corresponding widget based on user selection
|
||||
// "updated" is the 5th widget on the page, "added" is the 6th
|
||||
|
65
bridges/FM4Bridge.php
Normal file
65
bridges/FM4Bridge.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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);
|
||||
|
||||
$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);
|
||||
}
|
||||
|
||||
$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));
|
||||
}
|
||||
}
|
||||
}
|
119
bridges/FSecureBlogBridge.php
Normal file
119
bridges/FSecureBlogBridge.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
class FSecureBlogBridge extends BridgeAbstract {
|
||||
const NAME = 'F-Secure Blog';
|
||||
const URI = 'https://blog.f-secure.com';
|
||||
const DESCRIPTION = 'F-Secure Blog';
|
||||
const MAINTAINER = 'simon816';
|
||||
const PARAMETERS = array(
|
||||
'' => array(
|
||||
'categories' => array(
|
||||
'name' => 'Blog categories',
|
||||
'exampleValue' => 'home-security',
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
'oldest_date' => array(
|
||||
'name' => 'Oldest article date',
|
||||
'exampleValue' => '-2 months',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
public function getURI() {
|
||||
$lang = $this->getInput('language') or 'en';
|
||||
if ($lang === 'en') {
|
||||
return self::URI;
|
||||
}
|
||||
return self::URI . "/$lang";
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->items = array();
|
||||
$this->seen = array();
|
||||
|
||||
$this->oldest = strtotime($this->getInput('oldest_date')) ?: 0;
|
||||
|
||||
$categories = $this->getInput('categories');
|
||||
if (!empty($categories)) {
|
||||
foreach (explode(',', $categories) as $cat) {
|
||||
if (!empty($cat)) {
|
||||
$this->collectCategory($cat);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->getURI() . '/');
|
||||
|
||||
foreach ($html->find('ul.c-header-menu-desktop__list li a') as $link) {
|
||||
$url = parse_url($link->href);
|
||||
if (($pos = strpos($url['path'], '/category/')) !== false) {
|
||||
$cat = substr($url['path'], $pos + strlen('/category/'), -1);
|
||||
$this->collectCategory($cat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCategory($category) {
|
||||
$url = $this->getURI() . "/category/$category/";
|
||||
while ($url) {
|
||||
//Limit total amount of requests
|
||||
if(count($this->items) >= 20) {
|
||||
break;
|
||||
}
|
||||
$url = $this->collectListing($url);
|
||||
}
|
||||
}
|
||||
|
||||
// n.b. this relies on articles to be ordered by date so the cutoff works
|
||||
private function collectListing($url) {
|
||||
$html = getSimpleHTMLDOMCached($url, 60 * 60);
|
||||
$items = $html->find('section.b-blog .l-blog__content__listing div.c-listing-item');
|
||||
|
||||
$catName = trim($html->find('section.b-blog .c-blog-header__title', 0)->plaintext);
|
||||
|
||||
foreach ($items as $item) {
|
||||
$url = $item->getAttribute('data-url');
|
||||
if (!$this->collectArticle($url)) {
|
||||
return null; // Too old, stop collecting
|
||||
}
|
||||
}
|
||||
|
||||
// Point's to 404 for non-english blog
|
||||
// $next = $html->find('link[rel=next]', 0);
|
||||
$next = $html->find('ul.page-numbers a.next', 0);
|
||||
return $next ? $next->href : null;
|
||||
}
|
||||
|
||||
// Returns a boolean whether to continue collecting articles
|
||||
// i.e. date is after oldest cutoff
|
||||
private function collectArticle($url) {
|
||||
if (array_key_exists($url, $this->seen)) {
|
||||
return true;
|
||||
}
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
|
||||
$rssItem = array( 'uri' => $url, 'uid' => $url );
|
||||
$rssItem['title'] = $html->find('meta[property=og:title]', 0)->content;
|
||||
$dt = $html->find('meta[property=article:published_time]', 0)->content;
|
||||
// Exit if too old
|
||||
if (strtotime($dt) < $this->oldest) {
|
||||
return false;
|
||||
}
|
||||
$rssItem['timestamp'] = $dt;
|
||||
$img = $html->find('meta[property=og:image]', 0);
|
||||
$rssItem['enclosures'] = $img ? array($img->content) : array();
|
||||
$rssItem['author'] = trim($html->find('.c-blog-author__text a', 0)->plaintext);
|
||||
$rssItem['categories'] = array_map(function ($link) {
|
||||
return trim($link->plaintext);
|
||||
}, $html->find('.b-single-header__categories .c-category-list a'));
|
||||
$rssItem['content'] = trim($html->find('article', 0)->innertext);
|
||||
|
||||
$this->items[] = $rssItem;
|
||||
$this->seen[$url] = 1;
|
||||
return true;
|
||||
}
|
||||
}
|
@@ -6,8 +6,7 @@ class FabriceBellardBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'somini';
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not load content');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach ($html->find('p') as $obj) {
|
||||
$item = array();
|
||||
|
@@ -1,10 +1,10 @@
|
||||
<?php
|
||||
class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene, logmanoriginal';
|
||||
const NAME = 'Facebook Bridge';
|
||||
// const MAINTAINER = 'teromene, logmanoriginal';
|
||||
const NAME = 'Facebook Bridge | Main Site';
|
||||
const URI = 'https://www.facebook.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
|
||||
please insert the parameter as follow : myExamplePage/132621766841117';
|
||||
|
||||
@@ -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(
|
||||
@@ -66,14 +66,13 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
case 'User':
|
||||
if(!empty($this->authorName)) {
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||
. ' - ' . static::NAME;
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Group':
|
||||
if(!empty($this->groupName)) {
|
||||
return $this->groupName . ' - ' . static::NAME;
|
||||
return $this->groupName;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -82,6 +81,34 @@ class FacebookBridge extends BridgeAbstract {
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function detectParameters($url){
|
||||
$params = array();
|
||||
|
||||
// By profile
|
||||
$regex = '/^(https?:\/\/)?(www\.)?facebook\.com\/profile\.php\?id\=([^\/?&\n]+)?(.*)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['u'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By group
|
||||
$regex = '/^(https?:\/\/)?(www\.)?facebook\.com\/groups\/([^\/?\n]+)?(.*)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['g'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By username
|
||||
$regex = '/^(https?:\/\/)?(www\.)?facebook\.com\/([^\/?\n]+)/';
|
||||
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['u'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = self::URI;
|
||||
|
||||
@@ -148,8 +175,13 @@ class FacebookBridge extends BridgeAbstract {
|
||||
$header = array();
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
||||
$touchURI = str_replace(
|
||||
'https://www.facebook',
|
||||
'https://touch.facebook',
|
||||
$this->getURI()
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($touchURI, $header);
|
||||
|
||||
if(!$this->isPublicGroup($html)) {
|
||||
returnClientError('This group is not public! RSS-Bridge only supports public groups!');
|
||||
@@ -159,19 +191,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;
|
||||
|
||||
@@ -188,16 +219,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];
|
||||
|
||||
@@ -209,24 +231,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!');
|
||||
@@ -235,7 +280,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];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -244,57 +290,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,
|
||||
@@ -321,13 +371,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'] === '/') {
|
||||
@@ -489,8 +533,7 @@ EOD;
|
||||
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($captcha_action, $header, $opts)
|
||||
or returnServerError('Failed to submit captcha response back to Facebook');
|
||||
$html = getSimpleHTMLDOM($captcha_action, $header, $opts);
|
||||
|
||||
return $html;
|
||||
}
|
||||
@@ -515,8 +558,7 @@ EOD;
|
||||
$header = array();
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('No results for this query.');
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header);
|
||||
|
||||
}
|
||||
|
||||
@@ -528,7 +570,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) {
|
||||
@@ -674,8 +716,15 @@ EOD;
|
||||
|
||||
$uri = $post->find('abbr')[0]->parent()->getAttribute('href');
|
||||
|
||||
if (false !== strpos($uri, '?')) {
|
||||
$uri = substr($uri, 0, strpos($uri, '?'));
|
||||
// Extract fbid and patch link
|
||||
if (strpos($uri, '?') !== false) {
|
||||
$query = substr($uri, strpos($uri, '?') + 1);
|
||||
parse_str($query, $query_params);
|
||||
if (isset($query_params['story_fbid'])) {
|
||||
$uri = self::URI . $query_params['story_fbid'];
|
||||
} else {
|
||||
$uri = substr($uri, 0, strpos($uri, '?'));
|
||||
}
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
@@ -695,6 +744,7 @@ EOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion (User)
|
||||
|
||||
}
|
||||
|
@@ -35,6 +35,8 @@ class FicbookBridge extends BridgeAbstract {
|
||||
),
|
||||
);
|
||||
|
||||
protected $titleName;
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Site News': {
|
||||
@@ -56,15 +58,33 @@ 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');
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header);
|
||||
|
||||
$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 +104,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 +117,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 +150,10 @@ class FicbookBridge extends BridgeAbstract {
|
||||
'июня',
|
||||
'июля',
|
||||
'августа',
|
||||
'Сентября',
|
||||
'сентября',
|
||||
'октября',
|
||||
'Ноября',
|
||||
'Декабря',
|
||||
'ноября',
|
||||
'декабря',
|
||||
);
|
||||
|
||||
$en_month = array(
|
||||
|
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
class FierPandaBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'snroki';
|
||||
const NAME = 'Fier Panda Bridge';
|
||||
const URI = 'http://www.fier-panda.fr/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request Fier Panda.');
|
||||
|
||||
defaultLinkTo($html, static::URI);
|
||||
|
||||
foreach($html->find('article') as $article) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('a', 0)->href;
|
||||
$item['title'] = $article->find('a', 0)->title;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -2,11 +2,11 @@
|
||||
|
||||
class FilterBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'Frenzie';
|
||||
const MAINTAINER = 'Frenzie, ORelio';
|
||||
const NAME = 'Filter';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Filters a feed of your choice';
|
||||
const URI = 'https://github.com/rss-bridge/rss-bridge';
|
||||
const URI = 'https://github.com/RSS-Bridge/rss-bridge';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'url' => array(
|
||||
@@ -14,7 +14,7 @@ class FilterBridge extends FeedExpander {
|
||||
'required' => true,
|
||||
),
|
||||
'filter' => array(
|
||||
'name' => 'Filter item title (regular expression)',
|
||||
'name' => 'Filter (regular expression)',
|
||||
'required' => false,
|
||||
),
|
||||
'filter_type' => array(
|
||||
@@ -22,60 +22,101 @@ class FilterBridge extends FeedExpander {
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Permit' => 'permit',
|
||||
'Block' => 'block',
|
||||
'Keep matching items' => 'permit',
|
||||
'Hide matching items' => 'block',
|
||||
),
|
||||
'defaultValue' => 'permit',
|
||||
),
|
||||
'title_from_content' => array(
|
||||
'name' => 'Generate title from content',
|
||||
'case_insensitive' => array(
|
||||
'name' => 'Case-insensitive filter',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
)
|
||||
),
|
||||
'fix_encoding' => array(
|
||||
'name' => 'Attempt Latin1/UTF-8 fixes when evaluating filter',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
),
|
||||
'target_title' => array(
|
||||
'name' => 'Apply filter on title',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'target_content' => array(
|
||||
'name' => 'Apply filter on content',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
),
|
||||
'target_author' => array(
|
||||
'name' => 'Apply filter on author',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
),
|
||||
'title_from_content' => array(
|
||||
'name' => 'Generate title from content (overwrite existing title)',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
),
|
||||
'length_limit' => array(
|
||||
'name' => 'Max length analyzed by filter (-1: no limit)',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'defaultValue' => -1,
|
||||
),
|
||||
));
|
||||
|
||||
protected function parseItem($newItem){
|
||||
$item = parent::parseItem($newItem);
|
||||
|
||||
// Generate title from first 50 characters of content?
|
||||
if($this->getInput('title_from_content') && array_key_exists('content', $item)) {
|
||||
|
||||
$content = str_get_html($item['content']);
|
||||
|
||||
$pos = strpos($item['content'], ' ', 50);
|
||||
|
||||
$item['title'] = substr(
|
||||
$content->plaintext,
|
||||
0,
|
||||
$pos
|
||||
);
|
||||
|
||||
$item['title'] = substr($content->plaintext, 0, $pos);
|
||||
if(strlen($content->plaintext) >= $pos) {
|
||||
$item['title'] .= '...';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch(true) {
|
||||
case $this->getFilterType() === 'permit':
|
||||
if (preg_match($this->getFilter(), $item['title'])) {
|
||||
return $item;
|
||||
}
|
||||
break;
|
||||
case $this->getFilterType() === 'block':
|
||||
if (!preg_match($this->getFilter(), $item['title'])) {
|
||||
return $item;
|
||||
}
|
||||
break;
|
||||
// Build regular expression
|
||||
$regex = '/' . $this->getInput('filter') . '/';
|
||||
if($this->getInput('case_insensitive')) {
|
||||
$regex .= 'i';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getFilter(){
|
||||
return '/' . $this->getInput('filter') . '/';
|
||||
}
|
||||
// Retrieve fields to check
|
||||
$filter_fields = array();
|
||||
if($this->getInput('target_title')) {
|
||||
$filter_fields[] = $item['title'];
|
||||
}
|
||||
if($this->getInput('target_content')) {
|
||||
$filter_fields[] = $item['content'];
|
||||
}
|
||||
if($this->getInput('target_author')) {
|
||||
$filter_fields[] = $item['author'];
|
||||
}
|
||||
|
||||
protected function getFilterType(){
|
||||
return $this->getInput('filter_type');
|
||||
// Apply filter on item
|
||||
$keep_item = false;
|
||||
$length_limit = intval($this->getInput('length_limit'));
|
||||
foreach($filter_fields as $field) {
|
||||
if($length_limit > 0) {
|
||||
$field = substr($field, 0, $length_limit);
|
||||
}
|
||||
$keep_item |= boolval(preg_match($regex, $field));
|
||||
if($this->getInput('fix_encoding')) {
|
||||
$keep_item |= boolval(preg_match($regex, utf8_decode($field)));
|
||||
$keep_item |= boolval(preg_match($regex, utf8_encode($field)));
|
||||
}
|
||||
}
|
||||
|
||||
// Reverse result? (keep everything but matching items)
|
||||
if($this->getInput('filter_type') === 'block') {
|
||||
$keep_item = !$keep_item;
|
||||
}
|
||||
|
||||
return $keep_item ? $item : null;
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
@@ -84,18 +125,15 @@ class FilterBridge extends FeedExpander {
|
||||
if(empty($url)) {
|
||||
$url = parent::getURI();
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
if($this->getInput('url') && substr($this->getInput('url'), 0, strlen('http')) !== 'http') {
|
||||
// just in case someone find a way to access local files by playing with the url
|
||||
if($this->getInput('url') && substr($this->getInput('url'), 0, 4) !== 'http') {
|
||||
// just in case someone finds a way to access local files by playing with the url
|
||||
returnClientError('The url parameter must either refer to http or https protocol.');
|
||||
}
|
||||
try{
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
} catch (Exception $e) {
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
}
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user