mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-16 13:34:11 +02:00
Compare commits
396 Commits
2019-05-08
...
2020-11-10
Author | SHA1 | Date | |
---|---|---|---|
|
ff50e4918c | ||
|
8e4d6d8fdb | ||
|
cc548b16a8 | ||
|
b66026e241 | ||
|
a23d4bd0e6 | ||
|
bde4159a9e | ||
|
3ad138026d | ||
|
d05a8b79fe | ||
|
efe32aad22 | ||
|
0655b3cb39 | ||
|
59082368c7 | ||
|
c8b2c1bf74 | ||
|
b48bc77c22 | ||
|
6af87b2f32 | ||
|
93cdf5e342 | ||
|
164b407f28 | ||
|
2714c3d816 | ||
|
364b5282a3 | ||
|
5e4f3c351e | ||
|
a332a5a414 | ||
|
45e2f385b3 | ||
|
0a1ff10a52 | ||
|
645a8f62c6 | ||
|
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 | ||
|
b4f393a5cc | ||
|
29126ebe29 | ||
|
50c971d545 | ||
|
7aba7992aa | ||
|
48ebed7b38 | ||
|
ccef6b95ad | ||
|
dd5da99a30 | ||
|
2ff27b92ff | ||
|
b47189921f | ||
|
f1d3e8c9c9 | ||
|
53fbd2a5a0 | ||
|
3254a4d7bc | ||
|
52d2d21da5 | ||
|
abb74f056c | ||
|
25548b6757 | ||
|
cfe433e9e2 | ||
|
0dfc4ea2c5 | ||
|
38960df180 | ||
|
b440a6fdc6 | ||
|
48d0385653 | ||
|
b68c0e0df8 | ||
|
f27b267614 | ||
|
8bff63d9c6 | ||
|
2b4a030158 | ||
|
6a99904e64 | ||
|
f3c687604f | ||
|
a86a94555d | ||
|
acc0787b00 | ||
|
c8992650a1 | ||
|
f9f511a849 | ||
|
990719d614 | ||
|
b6be18d585 | ||
|
cf525c964a | ||
|
52a4f0860c | ||
|
21b27a1042 | ||
|
2eee535171 | ||
|
da51fc065f | ||
|
e032705c9a | ||
|
be27bc9250 | ||
|
75edc1b2b7 | ||
|
c9ea53806d | ||
|
2bb9480555 | ||
|
eb942bc498 | ||
|
5a0ea423c4 | ||
|
2120cc42fb | ||
|
5067501661 | ||
|
aea8484ccc | ||
|
6b9394dc78 | ||
|
4b51d42b8c | ||
|
d3fbf0d872 | ||
|
41a8eb74a1 | ||
|
7e6c58b67a | ||
|
a31e518a07 | ||
|
50162f52b6 | ||
|
c0edf6e424 | ||
|
2ea8d73ac1 | ||
|
465cd8c768 | ||
|
73f4bc078e | ||
|
1add201d3b | ||
|
09113c2594 | ||
|
c39e642877 | ||
|
e2460ead18 | ||
|
60c1339612 | ||
|
d324aa5da1 | ||
|
6f24987601 | ||
|
54fb29d443 | ||
|
ebe463dd08 | ||
|
987f42d6d4 | ||
|
fa8253c8bf | ||
|
e4444e6432 | ||
|
3769850ba3 | ||
|
89e3da0b6f | ||
|
99d4571c6b | ||
|
69acc6228a | ||
|
5e2f0fb626 | ||
|
372461b1a3 | ||
|
1591e18027 | ||
|
e2bca5bb05 | ||
|
7926ffad73 | ||
|
7ff97c0c7b | ||
|
1989252608 | ||
|
91e73b00b5 | ||
|
5c6c79baf4 | ||
|
99d1343045 | ||
|
14e6dbb645 | ||
|
fc8421ed50 | ||
|
2460b67886 | ||
|
705b9daa0b | ||
|
1ada9c26f8 | ||
|
55e1703741 | ||
|
849eaeb50e | ||
|
aeca4cfd60 | ||
|
686f21bc50 | ||
|
8dd8be9694 | ||
|
dfa9c651cd | ||
|
6d6d6037a3 | ||
|
2559dbbf49 | ||
|
de53120843 | ||
|
b1b7e4edce | ||
|
b27487ace0 | ||
|
d005acca83 | ||
|
93de8c239b | ||
|
75b0213684 | ||
|
f76a23f0a5 | ||
|
e4e04a7865 | ||
|
da339fd5cc | ||
|
ba116d9ab6 | ||
|
ea08445946 | ||
|
ade09b2aad | ||
|
28d46b6721 | ||
|
1efb7c7bce | ||
|
d34411137f | ||
|
70542686bb | ||
|
edf10be93a | ||
|
a725fdd315 | ||
|
84ba0c4a9e | ||
|
c17b864242 | ||
|
5ff3d0121c | ||
|
f00a054e0f | ||
|
5a9519967b | ||
|
17f587fcbe | ||
|
f28cbecc02 | ||
|
84450371b5 | ||
|
69dd33ac82 | ||
|
95388cdf44 | ||
|
b74dda7af9 | ||
|
ca1a5feba5 | ||
|
69a0498732 | ||
|
3d231a417f | ||
|
35bd706391 | ||
|
0e30468e0f | ||
|
ccf375e917 | ||
|
946a99d334 | ||
|
e2e0ced055 | ||
|
d4e867f240 | ||
|
b0a780acda | ||
|
1814116d67 | ||
|
d89326fe2d | ||
|
62198ecfa2 | ||
|
94e4ef8f27 | ||
|
6c4098d655 | ||
|
468d8be72d | ||
|
ed539bacf9 | ||
|
82a9bb5b1c | ||
|
15c374e317 | ||
|
052844f5e1 | ||
|
014b698f67 | ||
|
5656792cee | ||
|
66c5b732cf | ||
|
b889e867fd | ||
|
b519d350bf | ||
|
2a254855d8 | ||
|
72bcc173eb | ||
|
4a60f05fd6 | ||
|
84d48d5614 | ||
|
7cf898b5af | ||
|
16bd2aec7a | ||
|
3d87ecbf8c |
@@ -1,7 +1,14 @@
|
||||
.git
|
||||
.gitattributes
|
||||
.github/*
|
||||
.travis.yml
|
||||
cache/*
|
||||
CONTRIBUTING.md
|
||||
DEBUG
|
||||
Dockerfile
|
||||
whitelist.txt
|
||||
phpcompatibility.xml
|
||||
phpcs.xml
|
||||
CONTRIBUTING.md
|
||||
phpcs.xml
|
||||
scalingo.json
|
||||
tests/*
|
||||
whitelist.txt
|
77
.gitattributes
vendored
77
.gitattributes
vendored
@@ -10,27 +10,60 @@
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
# Ignore files in git archive (i.e. GitHub release builds)
|
||||
Dockerfile export-ignore
|
||||
.travis.yml export-ignore
|
||||
.github/ export-ignore
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
.dockerignore export-ignore
|
||||
scalingo.json export-ignore
|
||||
phpunit.xml export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpcompatibility.xml export-ignore
|
||||
tests/ export-ignore
|
||||
cache/.gitkeep export-ignore
|
||||
|
||||
## Docker
|
||||
Dockerfile export-ignore
|
||||
.dockerignore export-ignore
|
||||
|
||||
## Travis
|
||||
.travis.yml export-ignore
|
||||
|
||||
## GitHub
|
||||
.github/ export-ignore
|
||||
|
||||
## Git
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
## Scalingo
|
||||
scalingo.json export-ignore
|
||||
|
||||
## RSS-Bridge
|
||||
phpunit.xml export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpcompatibility.xml export-ignore
|
||||
tests/ export-ignore
|
||||
cache/.gitkeep export-ignore
|
||||
bridges/DemoBridge.php export-ignore
|
||||
bridges/FeedExpanderExampleBridge.php export-ignore
|
||||
|
||||
## Composer
|
||||
#
|
||||
# Keep the following lines commented out. Heroku does
|
||||
# not function if the composer files are ignored during
|
||||
# export. For more information see
|
||||
# https://github.com/rss-bridge/rss-bridge/issues/1165
|
||||
#
|
||||
# composer.json export-ignore
|
||||
# composer.lock export-ignore
|
||||
|
||||
## Heroku
|
||||
#
|
||||
# Keep the following line commented out. Heroku does
|
||||
# not function if app.json is ignored during export.
|
||||
# For more information see
|
||||
# https://github.com/rss-bridge/rss-bridge/issues/1165
|
||||
#
|
||||
# app.json export-ignore
|
||||
|
@@ -1,6 +1,9 @@
|
||||
---
|
||||
name: Bridge request template
|
||||
name: Bridge request
|
||||
about: Use this template for requesting a new bridge
|
||||
title: Bridge request for ...
|
||||
labels: Bridge-Request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
38
.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
---
|
||||
name: Bug report
|
||||
about: Create a report to help us improve
|
||||
title: ''
|
||||
labels: Bug-Report
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Describe the bug**
|
||||
A clear and concise description of what the bug is.
|
||||
|
||||
**To Reproduce**
|
||||
Steps to reproduce the behavior:
|
||||
1. Go to '...'
|
||||
2. Click on '....'
|
||||
3. Scroll down to '....'
|
||||
4. See error
|
||||
|
||||
**Expected behavior**
|
||||
A clear and concise description of what you expected to happen.
|
||||
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**Desktop (please complete the following information):**
|
||||
- OS: [e.g. iOS]
|
||||
- Browser [e.g. chrome, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Smartphone (please complete the following information):**
|
||||
- Device: [e.g. iPhone6]
|
||||
- OS: [e.g. iOS8.1]
|
||||
- Browser [e.g. stock browser, safari]
|
||||
- Version [e.g. 22]
|
||||
|
||||
**Additional context**
|
||||
Add any other context about the problem here.
|
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
---
|
||||
name: Feature request
|
||||
about: Suggest an idea for this project
|
||||
title: ''
|
||||
labels: Feature-Request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Is your feature request related to a problem? Please describe.**
|
||||
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||
|
||||
**Describe the solution you'd like**
|
||||
A clear and concise description of what you want to happen.
|
||||
|
||||
**Describe alternatives you've considered**
|
||||
A clear and concise description of any alternative solutions or features you've considered.
|
||||
|
||||
**Additional context**
|
||||
Add any other context or screenshots about the feature request here.
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -240,3 +240,6 @@ config.ini.php
|
||||
#Auth
|
||||
.htaccess
|
||||
.htpasswd
|
||||
|
||||
#Crawler
|
||||
robots.txt
|
||||
|
18
Dockerfile
18
Dockerfile
@@ -1,5 +1,17 @@
|
||||
FROM ulsmith/alpine-apache-php7
|
||||
FROM php:7-apache
|
||||
|
||||
COPY ./ /app/public/
|
||||
ENV APACHE_DOCUMENT_ROOT=/app
|
||||
|
||||
RUN chown -R apache:root /app/public
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
|
||||
&& apt-get --yes update \
|
||||
&& apt-get --yes --no-install-recommends install \
|
||||
zlib1g-dev \
|
||||
libmemcached-dev \
|
||||
&& pecl install memcached \
|
||||
&& docker-php-ext-enable memcached \
|
||||
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
|
||||
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
|
||||
&& sed -ri -e 's/(MinProtocol\s*=\s*)TLSv1\.2/\1None/' /etc/ssl/openssl.cnf \
|
||||
&& sed -ri -e 's/(CipherString\s*=\s*DEFAULT)@SECLEVEL=2/\1/' /etc/ssl/openssl.cnf
|
||||
|
||||
COPY --chown=www-data:www-data ./ /app/
|
266
README.md
266
README.md
@@ -1,8 +1,8 @@
|
||||
rss-bridge
|
||||

|
||||
===
|
||||
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://tracker.debian.org/pkg/rss-bridge) [](https://www.gnu.org/software/guix/packages/R/) [](https://travis-ci.org/RSS-Bridge/rss-bridge) [](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://tracker.debian.org/pkg/rss-bridge) [](https://www.gnu.org/software/guix/packages/R/) [](https://travis-ci.org/RSS-Bridge/rss-bridge) [](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).
|
||||
|
||||
@@ -15,7 +15,6 @@ Supported sites/pages (examples)
|
||||
* `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/)
|
||||
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
|
||||
* `GooglePlus` : Most recent posts of user timeline
|
||||
* `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
|
||||
@@ -66,6 +65,7 @@ RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
|
||||
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
|
||||
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
|
||||
- [`json`](https://secure.php.net/manual/en/book.json.php)
|
||||
- [`filter`](https://secure.php.net/manual/en/book.filter.php)
|
||||
- [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
|
||||
|
||||
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
|
||||
@@ -77,7 +77,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
|
||||
===
|
||||
@@ -85,7 +85,7 @@ Deploy
|
||||
Thanks to the community, hosting your own instance of RSS-Bridge is as easy as clicking a button!
|
||||
|
||||
[](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
|
||||
[](https://cloud.docker.com/stack/deploy/?repo=https://github.com/rss-bridge/rss-bridge)
|
||||
[](https://heroku.com/deploy)
|
||||
|
||||
Getting involved
|
||||
===
|
||||
@@ -111,107 +111,158 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
|
||||
-->
|
||||
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [adamchainz](https://github.com/adamchainz)
|
||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||
* [Albirew](https://github.com/Albirew)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [b1nj](https://github.com/b1nj)
|
||||
* [benasse](https://github.com/benasse)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
* [ckiw](https://github.com/ckiw)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [couraudt](https://github.com/couraudt)
|
||||
* [da2x](https://github.com/da2x)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [disk0x](https://github.com/disk0x)
|
||||
* [DJCrashdummy](https://github.com/DJCrashdummy)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [DnAp](https://github.com/DnAp)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
|
||||
* [em92](https://github.com/em92)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [fulmeek](https://github.com/fulmeek)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [klimplant](https://github.com/klimplant)
|
||||
* [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)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [Limero](https://github.com/Limero)
|
||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||
* [lorenzos](https://github.com/lorenzos)
|
||||
* [m0zes](https://github.com/m0zes)
|
||||
* [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)
|
||||
* [mickael-bertrand](https://github.com/mickael-bertrand)
|
||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [ObsidianWitch](https://github.com/ObsidianWitch)
|
||||
* [ORelio](https://github.com/ORelio)
|
||||
* [PaulVayssiere](https://github.com/PaulVayssiere)
|
||||
* [pellaeon](https://github.com/pellaeon)
|
||||
* [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)
|
||||
* [quentinus95](https://github.com/quentinus95)
|
||||
* [regisenguehard](https://github.com/regisenguehard)
|
||||
* [Riduidel](https://github.com/Riduidel)
|
||||
* [rogerdc](https://github.com/rogerdc)
|
||||
* [Roliga](https://github.com/Roliga)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [somini](https://github.com/somini)
|
||||
* [squeek502](https://github.com/squeek502)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
* [sublimz](https://github.com/sublimz)
|
||||
* [sysadminstory](https://github.com/sysadminstory)
|
||||
* [tameroski](https://github.com/tameroski)
|
||||
* [teromene](https://github.com/teromene)
|
||||
* [thefranke](https://github.com/thefranke)
|
||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||
* [triatic](https://github.com/triatic)
|
||||
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||
* [wtuuju](https://github.com/wtuuju)
|
||||
* [yardenac](https://github.com/yardenac)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [adamchainz](https://github.com/adamchainz)
|
||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||
* [Albirew](https://github.com/Albirew)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alex73](https://github.com/alex73)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||
* [arnd-s](https://github.com/arnd-s)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [AxorPL](https://github.com/AxorPL)
|
||||
* [ayacoo](https://github.com/ayacoo)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [azdkj532](https://github.com/azdkj532)
|
||||
* [b1nj](https://github.com/b1nj)
|
||||
* [benasse](https://github.com/benasse)
|
||||
* [Binnette](https://github.com/Binnette)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
* [ckiw](https://github.com/ckiw)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [couraudt](https://github.com/couraudt)
|
||||
* [csisoap](https://github.com/csisoap)
|
||||
* [cyberjacob](https://github.com/cyberjacob)
|
||||
* [da2x](https://github.com/da2x)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [dawidsowa](https://github.com/dawidsowa)
|
||||
* [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)
|
||||
* [em92](https://github.com/em92)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [fanch317](https://github.com/fanch317)
|
||||
* [floviolleau](https://github.com/floviolleau)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [fulmeek](https://github.com/fulmeek)
|
||||
* [ggiessen](https://github.com/ggiessen)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [gloony](https://github.com/gloony)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [gsantner](https://github.com/gsantner)
|
||||
* [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)
|
||||
* [jannyba](https://github.com/jannyba)
|
||||
* [JasonGhent](https://github.com/JasonGhent)
|
||||
* [jdesgats](https://github.com/jdesgats)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [johnnygroovy](https://github.com/johnnygroovy)
|
||||
* [johnpc](https://github.com/johnpc)
|
||||
* [joni1993](https://github.com/joni1993)
|
||||
* [joshcoales](https://github.com/joshcoales)
|
||||
* [klimplant](https://github.com/klimplant)
|
||||
* [kolarcz](https://github.com/kolarcz)
|
||||
* [kranack](https://github.com/kranack)
|
||||
* [kraoc](https://github.com/kraoc)
|
||||
* [l1n](https://github.com/l1n)
|
||||
* [laBecasse](https://github.com/laBecasse)
|
||||
* [lagaisse](https://github.com/lagaisse)
|
||||
* [lalannev](https://github.com/lalannev)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [Leomaradan](https://github.com/Leomaradan)
|
||||
* [liamka](https://github.com/liamka)
|
||||
* [Limero](https://github.com/Limero)
|
||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||
* [lorenzos](https://github.com/lorenzos)
|
||||
* [lukasklinger](https://github.com/lukasklinger)
|
||||
* [m0zes](https://github.com/m0zes)
|
||||
* [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)
|
||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mschwld](https://github.com/mschwld)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [Niehztog](https://github.com/Niehztog)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [ObsidianWitch](https://github.com/ObsidianWitch)
|
||||
* [OliverParoczai](https://github.com/OliverParoczai)
|
||||
* [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)
|
||||
* [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)
|
||||
* [quentinus95](https://github.com/quentinus95)
|
||||
* [RawkBob](https://github.com/RawkBob)
|
||||
* [regisenguehard](https://github.com/regisenguehard)
|
||||
* [Riduidel](https://github.com/Riduidel)
|
||||
* [rogerdc](https://github.com/rogerdc)
|
||||
* [Roliga](https://github.com/Roliga)
|
||||
* [ronansalmon](https://github.com/ronansalmon)
|
||||
* [rremizov](https://github.com/rremizov)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [shutosg](https://github.com/shutosg)
|
||||
* [Simounet](https://github.com/Simounet)
|
||||
* [somini](https://github.com/somini)
|
||||
* [squeek502](https://github.com/squeek502)
|
||||
* [stjohnjohnson](https://github.com/stjohnjohnson)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
* [sublimz](https://github.com/sublimz)
|
||||
* [sunchaserinfo](https://github.com/sunchaserinfo)
|
||||
* [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
* [sysadminstory](https://github.com/sysadminstory)
|
||||
* [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)
|
||||
* [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)
|
||||
* [yardenac](https://github.com/yardenac)
|
||||
* [ymeister](https://github.com/ymeister)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
|
||||
Licenses
|
||||
===
|
||||
@@ -220,6 +271,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)
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
@@ -19,13 +19,16 @@ class DetectAction extends ActionAbstract {
|
||||
$format = $this->userData['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
if(!Bridge::isWhitelisted($bridgeName)) {
|
||||
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
|
||||
|
||||
if(!$bridgeFac->isWhitelisted($bridgeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
$bridge = $bridgeFac->create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
continue;
|
||||
|
@@ -12,26 +12,32 @@
|
||||
*/
|
||||
|
||||
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;
|
||||
|
||||
$format = $this->userData['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
// DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
|
||||
// this is to keep compatibility until futher complete removal
|
||||
if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) {
|
||||
$format = substr($format, 0, $pos);
|
||||
}
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
// whitelist control
|
||||
if(!Bridge::isWhitelisted($bridge)) {
|
||||
if(!$bridgeFac->isWhitelisted($bridge)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
|
||||
// Data retrieval
|
||||
$bridge = Bridge::create($bridge);
|
||||
$bridge = $bridgeFac->create($bridge);
|
||||
|
||||
$noproxy = array_key_exists('_noproxy', $this->userData)
|
||||
&& filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
||||
@@ -85,7 +91,9 @@ class DisplayAction extends ActionAbstract {
|
||||
);
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope('');
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setKey($cache_params);
|
||||
@@ -147,63 +155,77 @@ class DisplayAction extends ActionAbstract {
|
||||
} 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
|
||||
@@ -216,7 +238,9 @@ class DisplayAction extends ActionAbstract {
|
||||
|
||||
// Data transformation
|
||||
try {
|
||||
$format = Format::create($format);
|
||||
$formatFac = new FormatFactory();
|
||||
$formatFac->setWorkingDir(PATH_LIB_FORMATS);
|
||||
$format = $formatFac->create($format);
|
||||
$format->setItems($items);
|
||||
$format->setExtraInfos($infos);
|
||||
$format->setLastModified($cache->getTime());
|
||||
|
@@ -17,9 +17,12 @@ class ListAction extends ActionAbstract {
|
||||
$list->bridges = array();
|
||||
$list->total = 0;
|
||||
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
|
||||
|
||||
$bridge = $bridgeFac->create($bridgeName);
|
||||
|
||||
if($bridge === false) { // Broken bridge, show as inactive
|
||||
|
||||
@@ -31,7 +34,7 @@ class ListAction extends ActionAbstract {
|
||||
|
||||
}
|
||||
|
||||
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||
$status = $bridgeFac->isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => $status,
|
||||
|
8
app.json
Normal file
8
app.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"service": "Heroku",
|
||||
"name": "RSS-Bridge",
|
||||
"description": "RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites which don't have one.",
|
||||
"repository": "https://github.com/RSS-Bridge/rss-bridge",
|
||||
"keywords": ["php", "rss-bridge", "rss"]
|
||||
}
|
||||
|
57
bridges/ASRockNewsBridge.php
Normal file
57
bridges/ASRockNewsBridge.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class ASRockNewsBridge extends BridgeAbstract {
|
||||
const NAME = 'ASRock News Bridge';
|
||||
const URI = 'https://www.asrock.com';
|
||||
const DESCRIPTION = 'Returns latest news articles';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array();
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp')
|
||||
or returnServerError('Could not request: ' . self::URI . '/news/index.asp');
|
||||
|
||||
$html = defaultLinkTo($html, self::URI . '/news/');
|
||||
|
||||
foreach($html->find('div.inner > a') as $index => $a) {
|
||||
$item = array();
|
||||
|
||||
$articlePath = $a->href;
|
||||
|
||||
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT)
|
||||
or returnServerError('Could not request: ' . $articlePath);
|
||||
|
||||
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
|
||||
|
||||
$contents = $articlePageHtml->find('div.Contents', 0);
|
||||
|
||||
$item['uri'] = $articlePath;
|
||||
$item['title'] = $contents->find('h5', 0)->innertext;
|
||||
|
||||
$contents->find('h5', 0)->outertext = '';
|
||||
|
||||
$item['content'] = $contents->innertext;
|
||||
$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
$item['enclosures'][] = $a->find('img', 0)->src;
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function extractDate($text) {
|
||||
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
|
||||
|
||||
$text = trim($text);
|
||||
|
||||
if (preg_match($dateRegex, $text, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
54
bridges/AirBreizhBridge.php
Normal file
54
bridges/AirBreizhBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
class AirBreizhBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'fanch317';
|
||||
const NAME = 'Air Breizh';
|
||||
const URI = 'https://www.airbreizh.asso.fr/';
|
||||
const DESCRIPTION = 'Returns newests publications on Air Breizh';
|
||||
const PARAMETERS = array(
|
||||
'Publications' => array(
|
||||
'theme' => array(
|
||||
'name' => 'Thematique',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Tout' => '',
|
||||
'Rapport d\'activite' => 'rapport-dactivite',
|
||||
'Etude' => 'etudes',
|
||||
'Information' => 'information',
|
||||
'Autres documents' => 'autres-documents',
|
||||
'Plan Régional de Surveillance de la qualité de l’air' => 'prsqa',
|
||||
'Transport' => 'transport'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme'))
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
foreach ($html->find('article') as $article) {
|
||||
$item = array();
|
||||
// Title
|
||||
$item['title'] = $article->find('h2', 0)->plaintext;
|
||||
// Author
|
||||
$item['author'] = 'Air Breizh';
|
||||
// Image
|
||||
$imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src');
|
||||
// Content preview
|
||||
$item['content'] = '<img src="' . $imagelink . '" />
|
||||
<br/>'
|
||||
. $article->find('.card__text', 0)->plaintext;
|
||||
// URL
|
||||
$item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
|
||||
// ID
|
||||
$item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
74
bridges/AlbionOnlineBridge.php
Normal file
74
bridges/AlbionOnlineBridge.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
class AlbionOnlineBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Albion Online Changelog';
|
||||
const MAINTAINER = 'otakuf';
|
||||
const URI = 'https://albiononline.com';
|
||||
const DESCRIPTION = 'Returns the changes made to the Albion Online';
|
||||
const CACHE_TIMEOUT = 3600; // 60min
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 5,
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Deutsch' => 'de',
|
||||
'Polski' => 'pl',
|
||||
'Français' => 'fr',
|
||||
'Русский' => 'ru',
|
||||
'Português' => 'pt',
|
||||
'Español' => 'es',
|
||||
),
|
||||
'title' => 'Language of changelog posts',
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full changelog',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Enable to receive the full changelog post for each item'
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
$api = 'https://albiononline.com/';
|
||||
// Example: https://albiononline.com/en/changelog/1/5
|
||||
$url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount');
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Unable to get changelog data from "' . $url . '"!');
|
||||
|
||||
foreach ($html->find('li') as $data) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href');
|
||||
$item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]);
|
||||
// Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language
|
||||
//print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') );
|
||||
//$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
$item['author'] = 'albiononline.com';
|
||||
if($this->getInput('full')) {
|
||||
$item['content'] = $this->getFullChangelog($item['uri']);
|
||||
} else {
|
||||
//$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext));
|
||||
// Just use title, no info at all or use title and date, see above
|
||||
$item['content'] = $item['title'];
|
||||
}
|
||||
$item['uid'] = hash('sha256', $item['title']);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getFullChangelog($url) {
|
||||
$html = getSimpleHTMLDOMCached($url)
|
||||
or returnServerError('Unable to load changelog post from "' . $url . '"!');
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
return $html->find('div.small-12.columns', 1)->innertext;
|
||||
}
|
||||
}
|
@@ -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();
|
||||
@@ -63,23 +85,23 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -134,11 +134,11 @@ 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;
|
||||
@@ -150,11 +150,11 @@ EOT;
|
||||
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
|
||||
|
||||
if (count($matches) === 3) {
|
||||
return [
|
||||
return array(
|
||||
'price' => $matches[2],
|
||||
'currency' => $matches[1],
|
||||
'shipping' => '0'
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@@ -3,7 +3,9 @@ class AnidexBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Anidex';
|
||||
const URI = 'https://anidex.info/';
|
||||
const URI = 'http://anidex.info/'; // anidex.info has ddos-guard so we need to use anidex.moe
|
||||
const ALTERNATE_URI = 'https://anidex.moe/'; // anidex.moe returns 301 unless Host is set to anidex.info
|
||||
const ALTERNATE_HOST = 'anidex.info'; // Correct host for requesting anidex.moe without 301 redirect
|
||||
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
@@ -108,7 +110,7 @@ class AnidexBridge extends BridgeAbstract {
|
||||
public function collectData() {
|
||||
|
||||
// Build Search URL from user-provided parameters
|
||||
$search_url = self::URI . '?s=upload_timestamp&o=desc';
|
||||
$search_url = self::ALTERNATE_URI . '?s=upload_timestamp&o=desc';
|
||||
foreach (array('id', 'lang_id', 'group_id') as $param_name) {
|
||||
$param = $this->getInput($param_name);
|
||||
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
|
||||
@@ -131,8 +133,16 @@ class AnidexBridge extends BridgeAbstract {
|
||||
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
|
||||
}
|
||||
|
||||
// We need to use a different Host HTTP header to reach the correct page on ALTERNATE_URI
|
||||
$headers = array('Host: ' . self::ALTERNATE_HOST);
|
||||
|
||||
// The HTTPS certificate presented by anidex.moe is for anidex.info. We need to ignore this.
|
||||
// As a consequence, the bridge is intentionally marked as insecure by setting self::URI to http://
|
||||
$opt[CURLOPT_SSL_VERIFYHOST] = 0;
|
||||
$opt[CURLOPT_SSL_VERIFYPEER] = 0;
|
||||
|
||||
// Retrieve torrent listing from search results, which does not contain torrent description
|
||||
$html = getSimpleHTMLDOM($search_url, array(), $opt)
|
||||
$html = getSimpleHTMLDOM($search_url, $headers, $opt)
|
||||
or returnServerError('Could not request Anidex: ' . $search_url);
|
||||
$links = $html->find('a');
|
||||
$results = array();
|
||||
@@ -156,10 +166,11 @@ class AnidexBridge extends BridgeAbstract {
|
||||
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
|
||||
|
||||
//Retrieve data for this torrent ID
|
||||
$item_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
$item_browse_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
$item_fetch_uri = self::ALTERNATE_URI . 'torrent/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
|
||||
//Retrieve full description from torrent page (cached for 24 hours: 86400 seconds)
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_fetch_uri, 86400, $headers, $opt)) {
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
|
||||
@@ -191,7 +202,7 @@ class AnidexBridge extends BridgeAbstract {
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
$item['uri'] = $item_uri;
|
||||
$item['uri'] = $item_browse_uri;
|
||||
$item['title'] = $item_title;
|
||||
$item['author'] = $item_author;
|
||||
$item['timestamp'] = $item_date;
|
||||
|
@@ -102,7 +102,6 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
$item_description = defaultLinkTo($item_description, self::URI);
|
||||
$item_description = str_replace("\r", '', $item_description);
|
||||
$item_description = str_replace("\n", '', $item_description);
|
||||
$item_description = utf8_encode($item_description);
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
|
149
bridges/AppleAppStoreBridge.php
Normal file
149
bridges/AppleAppStoreBridge.php
Normal file
@@ -0,0 +1,149 @@
|
||||
<?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'
|
||||
),
|
||||
'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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -5,19 +5,19 @@ class AppleMusicBridge extends BridgeAbstract {
|
||||
const URI = 'https://www.apple.com';
|
||||
const DESCRIPTION = 'Fetches the latest releases from an artist';
|
||||
const MAINTAINER = 'Limero';
|
||||
const PARAMETERS = [[
|
||||
'url' => [
|
||||
const PARAMETERS = array(array(
|
||||
'url' => array(
|
||||
'name' => 'Artist URL',
|
||||
'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274',
|
||||
'required' => true,
|
||||
],
|
||||
'imgSize' => [
|
||||
),
|
||||
'imgSize' => array(
|
||||
'name' => 'Image size for thumbnails (in px)',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 512,
|
||||
'required' => true,
|
||||
]
|
||||
]];
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
public function collectData() {
|
||||
@@ -36,12 +36,12 @@ class AppleMusicBridge extends BridgeAbstract {
|
||||
// Loop through each object
|
||||
foreach ($json->included as $obj) {
|
||||
if ($obj->type === 'lockup/album') {
|
||||
$this->items[] = [
|
||||
$this->items[] = array(
|
||||
'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;
|
||||
}
|
||||
@@ -49,9 +49,9 @@ class AppleMusicBridge extends BridgeAbstract {
|
||||
|
||||
// Add the images to each item
|
||||
foreach ($this->items as &$item) {
|
||||
$item['enclosures'] = [
|
||||
$item['enclosures'] = array(
|
||||
str_replace('{w}x{h}bb.{f}', $imgSize . 'x0w.jpg', $images[$item['enclosures']]),
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
// Sort the order to put the latest albums first
|
||||
|
@@ -91,7 +91,8 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
);
|
||||
|
||||
$input = getContents($url, $header) or die('Could not request ARTE.');
|
||||
$input = getContents($url, $header)
|
||||
or returnServerError('Could not request ARTE.');
|
||||
$input_json = json_decode($input, true);
|
||||
|
||||
foreach($input_json['videos'] as $element) {
|
||||
|
4638
bridges/AtmoNouvelleAquitaineBridge.php
Normal file
4638
bridges/AtmoNouvelleAquitaineBridge.php
Normal file
File diff suppressed because it is too large
Load Diff
58
bridges/AtmoOccitanieBridge.php
Normal file
58
bridges/AtmoOccitanieBridge.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?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)
|
||||
or returnServerError('Could not request ' . $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;
|
||||
}
|
||||
}
|
@@ -15,16 +15,6 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
|
||||
'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p'
|
||||
),
|
||||
'isDispo' => array(
|
||||
'name' => 'Disponibilité',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'-' => '',
|
||||
'En stock' => 1,
|
||||
'Sur commande' => 0
|
||||
),
|
||||
'title' => 'Critère de disponibilité'
|
||||
),
|
||||
'energy' => array(
|
||||
'name' => 'Carburant',
|
||||
'type' => 'list',
|
||||
@@ -87,111 +77,69 @@ class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
$model_url = self::URI . $this->getInput('url');
|
||||
|
||||
// Get the session cookies and the form token
|
||||
$this->getInitialParameters($model_url);
|
||||
// Build the GET data
|
||||
$get_data = 'form[energy]=' . $this->getInput('energy') .
|
||||
'&form[transmission]=' . $this->getInput('transmission') .
|
||||
'&form[priceMin]=' . $this->getInput('priceMin') .
|
||||
'&form[priceMin]=' . $this->getInput('priceMin');
|
||||
|
||||
// Build the form
|
||||
$post_data = array(
|
||||
'form[isDispo]' => $this->getInput('isDispo'),
|
||||
'form[energy]' => $this->getInput('energy'),
|
||||
'form[transmission]' => $this->getInput('transmission'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[_token]' => $this->token
|
||||
);
|
||||
|
||||
// Set the Form request content type
|
||||
// Set the header 'X-Requested-With' like the website does it
|
||||
$header = array(
|
||||
'Content-Type: application/x-www-form-urlencoded; charset=UTF-8',
|
||||
);
|
||||
|
||||
// Set the curl options (POST query and content, and session cookies
|
||||
$curl_opts = array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => http_build_query($post_data),
|
||||
CURLOPT_COOKIE => $this->cookies
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
);
|
||||
|
||||
// Get the JSON content of the form
|
||||
$json = getContents($model_url, $header, $curl_opts)
|
||||
$json = getContents($model_url . '?' . $get_data, $header)
|
||||
or returnServerError('Could not request AutoJM.');
|
||||
|
||||
// Extract the HTML content from the JSON result
|
||||
$data = json_decode($json);
|
||||
$html = str_get_html($data->content);
|
||||
$html = str_get_html($data->results);
|
||||
|
||||
// Go through every finisha of the model
|
||||
$list = $html->find('h2');
|
||||
foreach ($list as $finish) {
|
||||
$finish_name = $finish->plaintext;
|
||||
$motorizations = $finish->next_sibling()->find('li');
|
||||
foreach ($motorizations as $element) {
|
||||
$image = $element->find('div[class=block-product-image]', 0)->{'data-ga-banner'};
|
||||
$serie = $element->find('span[class=model]', 0)->plaintext;
|
||||
$url = self::URI . substr($element->find('a', 0)->href, 1);
|
||||
if ($element->find('span[class*=block-product-nbModel]', 0) != null) {
|
||||
$availability = 'En Stock';
|
||||
} else {
|
||||
$availability = 'Sur commande';
|
||||
}
|
||||
$discount_html = $element->find('span[class*=tag--promo]', 0);
|
||||
if ($discount_html != null) {
|
||||
$discount = $discount_html->plaintext;
|
||||
} else {
|
||||
$discount = 'inconnue';
|
||||
}
|
||||
$price = $element->find('span[class=price red h1]', 0)->plaintext;
|
||||
$item = array();
|
||||
$item['title'] = $finish_name . ' ' . $serie;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
|
||||
. $finish_name . ' ' . $serie . '</p>';
|
||||
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
|
||||
$item['content'] .= '<li>Série : ' . $serie . '</li>';
|
||||
$item['content'] .= '<li>Remise : ' . $discount . '</li>';
|
||||
$item['content'] .= '<li>Prix : ' . $price . '</li></ul>';
|
||||
// Go through every car of the model
|
||||
$list = $html->find('div[class=car-card]');
|
||||
foreach ($list as $car) {
|
||||
|
||||
// Add a fictionnal anchor to the RSS element URL, based on the item content ;
|
||||
// As the URL could be identical even if the price change, some RSS reader will not show those offers as new items
|
||||
$item['uri'] = $url . '#' . md5($item['content']);
|
||||
|
||||
$this->items[] = $item;
|
||||
// Get the Finish name if this car is the first of a new finish
|
||||
$prev_tag = $car->prev_sibling();
|
||||
if($prev_tag->tag == 'div' && $prev_tag->class == 'results-title') {
|
||||
$finish_name = $prev_tag->plaintext;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the session cookie and the form token
|
||||
*
|
||||
* @param string $pageURL The URL from which to get the values
|
||||
*/
|
||||
private function getInitialParameters($pageURL) {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $pageURL);
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$data = curl_exec($ch);
|
||||
|
||||
// Separate the response header and the content
|
||||
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||
$header = substr($data, 0, $headerSize);
|
||||
$content = substr($data, $headerSize);
|
||||
curl_close($ch);
|
||||
|
||||
// Extract the cookies from the headers
|
||||
$cookies = '';
|
||||
$http_response_header = explode("\r\n", $header);
|
||||
foreach ($http_response_header as $hdr) {
|
||||
if (strpos($hdr, 'Set-Cookie') !== false) {
|
||||
$cLine = explode(':', $hdr)[1];
|
||||
$cLine = explode(';', $cLine)[0];
|
||||
$cookies .= ';' . $cLine;
|
||||
// Get the info about the car offer
|
||||
$image = $car->find('div[class=car-card__visual]', 0)->find('img', 0)->src;
|
||||
$serie = $car->find('div[class=car-card__title]', 0)->plaintext;
|
||||
$url = $car->find('a', 0)->href;
|
||||
// Check if the car model is in stock or available only on order
|
||||
if($car->find('span[class*=tag--dispo]', 0) != null) {
|
||||
$availability = 'En Stock';
|
||||
} else {
|
||||
$availability = 'Sur commande';
|
||||
}
|
||||
}
|
||||
$this->cookies = trim(substr($cookies, 1));
|
||||
$discount_html = $car->find('span[class=promo]', 0);
|
||||
// Check if there is any discount dsiplayed
|
||||
if ($discount_html != null) {
|
||||
$discount = $discount_html->plaintext;
|
||||
} else {
|
||||
$discount = 'inconnue';
|
||||
}
|
||||
$price = $car->find('span[class=price]', 0)->plaintext;
|
||||
|
||||
// Get the token from the content
|
||||
$html = str_get_html($content);
|
||||
$token = $html->find('input[type=hidden][id=form__token]', 0);
|
||||
$this->token = $token->value;
|
||||
// Construct the new item
|
||||
$item = array();
|
||||
$item['title'] = $finish_name . ' ' . $serie;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
|
||||
. $finish_name . ' ' . $serie . '</p>';
|
||||
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
|
||||
$item['content'] .= '<li>Série : ' . $serie . '</li>';
|
||||
$item['content'] .= '<li>Remise : ' . $discount . '</li>';
|
||||
$item['content'] .= '<li>Prix : ' . $price . '</li></ul>';
|
||||
|
||||
// Add a fictionnal anchor to the RSS element URL, based on the item content ;
|
||||
// As the URL could be identical even if the price change, some RSS reader will not show those offers as new items
|
||||
$item['uri'] = $url . '#' . md5($item['content']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
55
bridges/AwwwardsBridge.php
Normal file
55
bridges/AwwwardsBridge.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
class AwwwardsBridge extends BridgeAbstract {
|
||||
const NAME = 'Awwwards';
|
||||
const URI = 'https://www.awwwards.com/';
|
||||
const DESCRIPTION = 'Fetches the latest ten sites of the day from Awwwards';
|
||||
const MAINTAINER = 'Paroleen';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const SITESURI = 'https://www.awwwards.com/websites/sites_of_the_day/';
|
||||
const SITEURI = 'https://www.awwwards.com/sites/';
|
||||
const ASSETSURI = 'https://assets.awwwards.com/awards/media/cache/thumb_417_299/';
|
||||
|
||||
private $sites = array();
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.awwwards.com/favicon.ico';
|
||||
}
|
||||
|
||||
private function fetchSites() {
|
||||
Debug::log('Fetching all sites');
|
||||
$sites = getSimpleHTMLDOM(self::SITESURI)
|
||||
or returnServerError('Could not fetch JSON for sites.');
|
||||
|
||||
Debug::log('Parsing all JSON data');
|
||||
foreach($sites->find('li[data-model]') as $site) {
|
||||
$decode = html_entity_decode($site->attr['data-model'],
|
||||
ENT_QUOTES, 'utf-8');
|
||||
$decode = json_decode($decode, true);
|
||||
$this->sites[] = $decode;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->fetchSites();
|
||||
|
||||
Debug::log('Building RSS feed');
|
||||
foreach($this->sites as $site) {
|
||||
$item = array();
|
||||
$item['title'] = $site['title'];
|
||||
$item['timestamp'] = $site['createdAt'];
|
||||
$item['categories'] = $site['tags'];
|
||||
|
||||
$item['content'] = '<img src="'
|
||||
. self::ASSETSURI
|
||||
. $site['images']['thumbnail']
|
||||
. '">';
|
||||
$item['uri'] = self::SITEURI . $site['slug'];
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if(count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -55,9 +55,7 @@ class BAEBridge extends BridgeAbstract {
|
||||
|
||||
$content .= '<hr>';
|
||||
$content .= $htmlDetail->find('section', 0)->innertext;
|
||||
$content = str_replace('src="/', 'src="' . parent::getURI() . '/', $content);
|
||||
$content = str_replace('href="/', 'href="' . parent::getURI() . '/', $content);
|
||||
$item['content'] = $content;
|
||||
$item['content'] = defaultLinkTo($content, parent::getURI());
|
||||
$image = $htmlDetail->find('#zoom', 0);
|
||||
if ($image) {
|
||||
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
|
||||
|
@@ -1,73 +1,262 @@
|
||||
<?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 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)
|
||||
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
|
||||
);
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title";
|
||||
$item['enclosures'] = array($img);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
case 'By band':
|
||||
case 'By album':
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
|
||||
|
||||
$titleElement = $html->find('head meta[name=title]', 0)
|
||||
or returnServerError('Unable to find title on: ' . $this->getURI());
|
||||
$this->feedName = $titleElement->content;
|
||||
|
||||
$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':
|
||||
$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 +270,94 @@ 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))
|
||||
or returnServerError('API request to "' . $url . '" failed.');
|
||||
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 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 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,17 +3,11 @@ 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.');
|
||||
|
||||
@@ -25,7 +19,13 @@ class BastaBridge extends BridgeAbstract {
|
||||
$item['title'] = $element->find('title', 0)->innertext;
|
||||
$item['uri'] = $element->find('guid', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
|
||||
$item['content'] = replaceImageUrl(getSimpleHTMLDOM($item['uri'])->find('div.texte', 0)->innertext);
|
||||
// Replaces all relative image URLs by absolute URLs.
|
||||
// Relative URLs always start with 'local/'!
|
||||
$item['content'] = preg_replace(
|
||||
'/src=["\']{1}([^"\']+)/ims',
|
||||
'src=\'' . self::URI . '$1\'',
|
||||
getSimpleHTMLDOM($item['uri'])->find('div.texte', 0)->innertext
|
||||
);
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
|
103
bridges/BinanceBridge.php
Normal file
103
bridges/BinanceBridge.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
class BinanceBridge extends BridgeAbstract {
|
||||
const NAME = 'Binance';
|
||||
const URI = 'https://www.binance.com';
|
||||
const DESCRIPTION = 'Subscribe to the Binance blog or the Binance Zendesk announcements.';
|
||||
const MAINTAINER = 'thefranke';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
'name' => 'category',
|
||||
'type' => 'list',
|
||||
'exampleValue' => 'Blog',
|
||||
'title' => 'Select a category',
|
||||
'values' => array(
|
||||
'Blog' => 'Blog',
|
||||
'Announcements' => 'Announcements'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://bin.bnbstatic.com/static/images/common/favicon.ico';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return self::NAME . ' ' . $this->getInput('category');
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if ($this->getInput('category') == 'Blog')
|
||||
return self::URI . '/en/blog';
|
||||
else
|
||||
return 'https://binance.zendesk.com/hc/en-us/categories/115000056351-Announcements';
|
||||
}
|
||||
|
||||
protected function collectBlogData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not fetch Binance blog data.');
|
||||
|
||||
foreach($html->find('div[direction="row"]') as $element) {
|
||||
|
||||
$date = $element->find('div[direction="column"]', 0);
|
||||
$day = $date->find('div', 0)->innertext;
|
||||
$month = $date->find('div', 1)->innertext;
|
||||
$extractedDate = $day . ' ' . $month;
|
||||
|
||||
$abstract = $element->find('div[direction="column"]', 1);
|
||||
$a = $abstract->find('a', 0);
|
||||
$uri = self::URI . $a->href;
|
||||
$title = $a->innertext;
|
||||
|
||||
$full = getSimpleHTMLDOMCached($uri);
|
||||
$content = $full->find('div.desc', 1);
|
||||
|
||||
$item = array();
|
||||
$item['title'] = $title;
|
||||
$item['uri'] = $uri;
|
||||
$item['timestamp'] = strtotime($extractedDate);
|
||||
$item['author'] = 'Binance';
|
||||
$item['content'] = $content;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function collectAnnouncementData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not fetch Zendesk announcement data.');
|
||||
|
||||
foreach($html->find('a.article-list-link') as $a) {
|
||||
$title = $a->innertext;
|
||||
$uri = 'https://binance.zendesk.com' . $a->href;
|
||||
|
||||
$full = getSimpleHTMLDOMCached($uri);
|
||||
$content = $full->find('div.article-body', 0);
|
||||
$date = $full->find('time', 0)->getAttribute('datetime');
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $title;
|
||||
$item['uri'] = $uri;
|
||||
$item['timestamp'] = strtotime($date);
|
||||
$item['author'] = 'Binance';
|
||||
$item['content'] = $content;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
if ($this->getInput('category') == 'Blog')
|
||||
$this->collectBlogData();
|
||||
else
|
||||
$this->collectAnnouncementData();
|
||||
}
|
||||
}
|
@@ -92,7 +92,7 @@ class BingSearchBridge extends BridgeAbstract
|
||||
or returnServerError('Could not request ' . self::NAME);
|
||||
$sizeKey = $this->getInput('image_size');
|
||||
|
||||
$items = [];
|
||||
$items = array();
|
||||
foreach ($html->find('a.iusc') as $element) {
|
||||
$data = json_decode(htmlspecialchars_decode($element->getAttribute('m')), true);
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
159
bridges/BrutBridge.php
Normal file
159
bridges/BrutBridge.php
Normal file
@@ -0,0 +1,159 @@
|
||||
<?php
|
||||
class BrutBridge extends BridgeAbstract {
|
||||
const NAME = 'Brut Bridge';
|
||||
const URI = 'https://www.brut.media';
|
||||
const DESCRIPTION = 'Returns 5 newest videos by category and edition';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'News' => 'news',
|
||||
'International' => 'international',
|
||||
'Economy' => 'economy',
|
||||
'Science and Technology' => 'science-and-technology',
|
||||
'Entertainment' => 'entertainment',
|
||||
'Sports' => 'sport',
|
||||
'Nature' => 'nature',
|
||||
'Health' => 'health',
|
||||
),
|
||||
'defaultValue' => 'news',
|
||||
),
|
||||
'edition' => array(
|
||||
'name' => ' Edition',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'United States' => 'us',
|
||||
'United Kingdom' => 'uk',
|
||||
'France' => 'fr',
|
||||
'Spain' => 'es',
|
||||
'India' => 'in',
|
||||
'Mexico' => 'mx',
|
||||
),
|
||||
'defaultValue' => 'us',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 1800; // 30 mins
|
||||
|
||||
private $videoId = '';
|
||||
private $videoType = '';
|
||||
private $videoImage = '';
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
$results = $html->find('div.results', 0);
|
||||
|
||||
foreach($results->find('li.col-6.col-sm-4.col-md-3.col-lg-2.px-2.pb-4') as $index => $li) {
|
||||
$item = array();
|
||||
|
||||
$videoPath = self::URI . $li->children(0)->href;
|
||||
|
||||
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600)
|
||||
or returnServerError('Could not request: ' . $videoPath);
|
||||
|
||||
$this->videoImage = $videoPageHtml->find('meta[name="twitter:image"]', 0)->content;
|
||||
|
||||
$this->processTwitterImage();
|
||||
|
||||
$description = $videoPageHtml->find('div.description', 0);
|
||||
|
||||
$item['uri'] = $videoPath;
|
||||
$item['title'] = $description->find('h1', 0)->plaintext;
|
||||
|
||||
if ($description->find('div.date', 0)->children(0)) {
|
||||
$description->find('div.date', 0)->children(0)->outertext = '';
|
||||
}
|
||||
|
||||
$item['content'] = $this->processContent(
|
||||
$description
|
||||
);
|
||||
|
||||
$item['timestamp'] = $this->processDate($description);
|
||||
$item['enclosures'][] = $this->videoImage;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
|
||||
return self::URI . '/' . $this->getInput('edition') . '/' . $this->getInput('category');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
|
||||
$parameters = $this->getParameters();
|
||||
|
||||
$editionValues = array_flip($parameters[0]['edition']['values']);
|
||||
$categoryValues = array_flip($parameters[0]['category']['values']);
|
||||
|
||||
return $categoryValues[$this->getInput('category')] . ' - ' .
|
||||
$editionValues[$this->getInput('edition')] . ' - Brut.';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function processDate($description) {
|
||||
|
||||
if ($this->getInput('edition') === 'uk') {
|
||||
$date = DateTime::createFromFormat('d/m/Y H:i', $description->find('div.date', 0)->innertext);
|
||||
return strtotime($date->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
return strtotime($description->find('div.date', 0)->innertext);
|
||||
}
|
||||
|
||||
private function processContent($description) {
|
||||
|
||||
$content = '<video controls poster="' . $this->videoImage . '" preload="none">
|
||||
<source src="https://content.brut.media/video/' . $this->videoId . '-' . $this->videoType . '-web.mp4"
|
||||
type="video/mp4">
|
||||
</video>';
|
||||
$content .= '<p>' . $description->find('h2.mb-1', 0)->innertext . '</p>';
|
||||
|
||||
if ($description->find('div.text.pb-3', 0)->children(1)->class != 'date') {
|
||||
$content .= '<p>' . $description->find('div.text.pb-3', 0)->children(1)->innertext . '</p>';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function processTwitterImage() {
|
||||
/**
|
||||
* Extract video ID + type from twitter image
|
||||
*
|
||||
* Example (wrapped):
|
||||
* https://img.brut.media/thumbnail/
|
||||
* the-life-of-rita-moreno-2cce75b5-d448-44d2-a97c-ca50d6470dd4-square.jpg
|
||||
* ?ts=1559337892
|
||||
*/
|
||||
$fpath = parse_url($this->videoImage, PHP_URL_PATH);
|
||||
$fname = basename($fpath);
|
||||
$fname = substr($fname, 0, strrpos($fname, '.'));
|
||||
$parts = explode('-', $fname);
|
||||
|
||||
if (end($parts) === 'auto') {
|
||||
$key = array_search('auto', $parts);
|
||||
unset($parts[$key]);
|
||||
}
|
||||
|
||||
$this->videoId = implode('-', array_splice($parts, -6, 5));
|
||||
$this->videoType = end($parts);
|
||||
}
|
||||
}
|
63
bridges/CNETFranceBridge.php
Normal file
63
bridges/CNETFranceBridge.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
class CNETFranceBridge extends FeedExpander
|
||||
{
|
||||
const MAINTAINER = 'leomaradan';
|
||||
const NAME = 'CNET France';
|
||||
const URI = 'https://www.cnetfrance.fr/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'CNET France RSS with filters';
|
||||
const PARAMETERS = array(
|
||||
'filters' => array(
|
||||
'title' => array(
|
||||
'name' => 'Exclude by title',
|
||||
'required' => false,
|
||||
'title' => 'Title term, separated by semicolon (;)',
|
||||
'defaultValue' => 'bon plan;bons plans;au meilleur prix;des meilleures offres;Amazon Prime Day;RED by SFR ou B&You'
|
||||
),
|
||||
'url' => array(
|
||||
'name' => 'Exclude by url',
|
||||
'required' => false,
|
||||
'title' => 'URL term, separated by semicolon (;)',
|
||||
'defaultValue' => 'bon-plan;bons-plans'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $bannedTitle = array();
|
||||
private $bannedURL = array();
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$title = $this->getInput('title');
|
||||
$url = $this->getInput('url');
|
||||
|
||||
if ($title !== null) {
|
||||
$this->bannedTitle = explode(';', $title);
|
||||
}
|
||||
|
||||
if ($url !== null) {
|
||||
$this->bannedURL = explode(';', $url);
|
||||
}
|
||||
|
||||
$this->collectExpandableDatas('https://www.cnetfrance.fr/feeds/rss/news/');
|
||||
}
|
||||
|
||||
protected function parseItem($feedItem)
|
||||
{
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
foreach ($this->bannedTitle as $term) {
|
||||
if (preg_match('/' . $term . '/mi', $item['title']) === 1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->bannedURL as $term) {
|
||||
if (preg_match('/' . $term . '/mi', $item['uri']) === 1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -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,7 +83,7 @@ class CastorusBridge extends BridgeAbstract {
|
||||
if(!$html)
|
||||
returnServerError('Could not load data from ' . self::URI . '!');
|
||||
|
||||
$activities = $html->find('div#activite/li');
|
||||
$activities = $html->find('div#activite > li');
|
||||
|
||||
if(!$activities)
|
||||
returnServerError('Failed to find activities!');
|
||||
|
84
bridges/CeskaTelevizeBridge.php
Executable file
84
bridges/CeskaTelevizeBridge.php
Executable file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
class CeskaTelevizeBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Česká televize Bridge';
|
||||
const URI = 'https://www.ceskatelevize.cz';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'Return newest videos';
|
||||
const MAINTAINER = 'kolarcz';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'url' => array(
|
||||
'name' => 'url to the show',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/dily/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function fixChars($text) {
|
||||
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
private function getUploadTimeFromString($string) {
|
||||
if (strpos($string, 'dnes') !== false) {
|
||||
return strtotime('today');
|
||||
} elseif (strpos($string, 'včera') !== false) {
|
||||
return strtotime('yesterday');
|
||||
} elseif (!preg_match('/(\d+).\s(\d+).(\s(\d+))?/', $string, $match)) {
|
||||
returnServerError('Could not get date from Česká televize string');
|
||||
}
|
||||
|
||||
$date = sprintf('%04d-%02d-%02d', isset($match[3]) ? $match[3] : date('Y'), $match[2], $match[1]);
|
||||
return strtotime($date);
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
|
||||
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(dily\/((nove|vysilani)\/)?)?$/';
|
||||
if (!preg_match($validUrl, $url, $match)) {
|
||||
returnServerError('Invalid url');
|
||||
}
|
||||
|
||||
$category = isset($match[4]) ? $match[4] : 'nove';
|
||||
$fixedUrl = "{$match[1]}dily/{$category}/";
|
||||
|
||||
$html = getSimpleHTMLDOM($fixedUrl)
|
||||
or returnServerError('Could not request Česká televize');
|
||||
|
||||
$this->feedUri = $fixedUrl;
|
||||
$this->feedName = str_replace('Přehled dílů — ', '', $this->fixChars($html->find('title', 0)->plaintext));
|
||||
if ($category !== 'nove') {
|
||||
$this->feedName .= " ({$category})";
|
||||
}
|
||||
|
||||
foreach ($html->find('.episodes-broadcast-content a.episode_list_item') as $element) {
|
||||
$itemTitle = $element->find('.episode_list_item-title', 0);
|
||||
$itemContent = $element->find('.episode_list_item-desc', 0);
|
||||
$itemDate = $element->find('.episode_list_item-date', 0);
|
||||
$itemThumbnail = $element->find('img', 0);
|
||||
$itemUri = self::URI . $element->getAttribute('href');
|
||||
|
||||
$item = array(
|
||||
'title' => $this->fixChars($itemTitle->plaintext),
|
||||
'uri' => $itemUri,
|
||||
'content' => '<img src="https:' . $itemThumbnail->getAttribute('src') . '" /><br />'
|
||||
. $this->fixChars($itemContent->plaintext),
|
||||
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext)
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return isset($this->feedName) ? $this->feedName : parent::getName();
|
||||
}
|
||||
}
|
@@ -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';
|
||||
|
||||
|
65
bridges/ComicsKingdomBridge.php
Normal file
65
bridges/ComicsKingdomBridge.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?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)
|
||||
or returnServerError('Could not request Comics Kingdom: ' . $this->getURI());
|
||||
|
||||
// Get author from first page
|
||||
$author = $html->find('div.author p', 0)->plaintext
|
||||
or returnServerError('Comics Kingdom comic does not exist: ' . $this->getURI());;
|
||||
|
||||
// Get current date/link
|
||||
$link = $html->find('meta[property=og:url]', 0)->content;
|
||||
for($i = 0; $i < 5; $i++) {
|
||||
$item = array();
|
||||
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request Comics Kingdom: ' . $link);
|
||||
|
||||
$imagelink = $page->find('meta[property=og:image]', 0)->content;
|
||||
$prevSlug = $page->find('slider-arrow[:is-left-arrow=true]', 0);
|
||||
$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,20 +10,20 @@ 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)
|
||||
@@ -39,7 +39,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;
|
||||
|
109
bridges/CuriousCatBridge.php
Normal file
109
bridges/CuriousCatBridge.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
class CuriousCatBridge extends BridgeAbstract {
|
||||
const NAME = 'Curious Cat Bridge';
|
||||
const URI = 'https://curiouscat.me';
|
||||
const DESCRIPTION = 'Returns list of newest questions and answers for a user profile';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'koethekoethe',
|
||||
)
|
||||
));
|
||||
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
|
||||
|
||||
$apiJson = getContents($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
|
||||
$apiData = json_decode($apiJson, true);
|
||||
|
||||
foreach($apiData['posts'] as $post) {
|
||||
$item = array();
|
||||
|
||||
$item['author'] = 'Anonymous';
|
||||
|
||||
if ($post['senderData']['id'] !== false) {
|
||||
$item['author'] = $post['senderData']['username'];
|
||||
}
|
||||
|
||||
$item['uri'] = $this->getURI() . '/post/' . $post['id'];
|
||||
$item['title'] = $this->ellipsisTitle($post['comment']);
|
||||
|
||||
$item['content'] = $this->processContent($post);
|
||||
$item['timestamp'] = $post['timestamp'];
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('username'))) {
|
||||
return self::URI . '/' . $this->getInput('username');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('username'))) {
|
||||
return $this->getInput('username') . ' - Curious Cat';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function processContent($post) {
|
||||
|
||||
$author = 'Anonymous';
|
||||
|
||||
if ($post['senderData']['id'] !== false) {
|
||||
$authorUrl = self::URI . '/' . $post['senderData']['username'];
|
||||
|
||||
$author = <<<EOD
|
||||
<a href="{$authorUrl}">{$post['senderData']['username']}</a>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$question = $this->formatUrls($post['comment']);
|
||||
$answer = $this->formatUrls($post['reply']);
|
||||
|
||||
$content = <<<EOD
|
||||
<p>{$author} asked:</p>
|
||||
<blockquote>{$question}</blockquote><br/>
|
||||
<p>{$post['addresseeData']['username']} answered:</p>
|
||||
<blockquote>{$answer}</blockquote>
|
||||
EOD;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function ellipsisTitle($text) {
|
||||
$length = 150;
|
||||
|
||||
if (strlen($text) > $length) {
|
||||
$text = explode('<br>', wordwrap($text, $length, '<br>'));
|
||||
return $text[0] . '...';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function formatUrls($content) {
|
||||
|
||||
return preg_replace(
|
||||
'/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims',
|
||||
'<a target="_blank" href="$1" target="_blank">$1</a> ',
|
||||
$content
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Dailymotion Bridge';
|
||||
const URI = 'https://www.dailymotion.com/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns the 5 newest videos by username/playlist or search';
|
||||
|
||||
const PARAMETERS = array (
|
||||
@@ -27,74 +27,99 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
),
|
||||
'pa' => array(
|
||||
'name' => 'Page',
|
||||
'type' => 'number'
|
||||
'type' => 'number',
|
||||
'defaultValue' => 1,
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected function getMetadata($id){
|
||||
$metadata = array();
|
||||
$html2 = getSimpleHTMLDOM(self::URI . 'video/' . $id);
|
||||
if(!$html2) {
|
||||
return $metadata;
|
||||
}
|
||||
private $feedName = '';
|
||||
|
||||
$metadata['title'] = $html2->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
$metadata['timestamp'] = strtotime(
|
||||
$html2->find('meta[property=video:release_date]', 0)->getAttribute('content')
|
||||
);
|
||||
$metadata['thumbnailUri'] = $html2->find('meta[property=og:image]', 0)->getAttribute('content');
|
||||
$metadata['uri'] = $html2->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||
return $metadata;
|
||||
}
|
||||
private $apiUrl = 'https://api.dailymotion.com';
|
||||
private $apiFields = 'created_time,description,id,owner.screenname,tags,thumbnail_url,title,url';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$limit = 5;
|
||||
$count = 0;
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request Dailymotion.');
|
||||
if ($this->queriedContext === 'By username' || $this->queriedContext === 'By playlist id') {
|
||||
|
||||
foreach($html->find('div.media a.preview_link') as $element) {
|
||||
if($count < $limit) {
|
||||
$apiJson = getContents($this->getApiUrl())
|
||||
or returnServerError('Could not request: ' . $this->getApiUrl());
|
||||
|
||||
$apiData = json_decode($apiJson, true);
|
||||
|
||||
$this->feedName = $this->getPlaylistTitle($this->getInput('p'));
|
||||
|
||||
foreach ($apiData['list'] as $apiItem) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $apiItem['url'];
|
||||
$item['uid'] = $apiItem['id'];
|
||||
$item['title'] = $apiItem['title'];
|
||||
$item['timestamp'] = $apiItem['created_time'];
|
||||
$item['author'] = $apiItem['owner.screenname'];
|
||||
$item['content'] = '<p><a href="' . $apiItem['url'] . '">
|
||||
<img src="' . $apiItem['thumbnail_url'] . '"></a></p><p>' . $apiItem['description'] . '</p>';
|
||||
$item['categories'] = $apiItem['tags'];
|
||||
$item['enclosures'][] = $apiItem['thumbnail_url'];
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->queriedContext === 'From search results') {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request Dailymotion.');
|
||||
|
||||
foreach($html->find('div.media a.preview_link') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item['id'] = str_replace('/video/', '', strtok($element->href, '_'));
|
||||
$metadata = $this->getMetadata($item['id']);
|
||||
|
||||
if(empty($metadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['uri'] = $metadata['uri'];
|
||||
$item['title'] = $metadata['title'];
|
||||
$item['timestamp'] = $metadata['timestamp'];
|
||||
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $metadata['thumbnailUri']
|
||||
. '" /></a><br><a href="'
|
||||
. $item['uri']
|
||||
. '">'
|
||||
. $item['title']
|
||||
. '</a>';
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $metadata['thumbnailUri']
|
||||
. '" /></a><br><a href="'
|
||||
. $item['uri']
|
||||
. '">'
|
||||
. $item['title']
|
||||
. '</a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
$count++;
|
||||
|
||||
if (count($this->items) >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
$specific = $this->getInput('u');
|
||||
break;
|
||||
case 'By playlist id':
|
||||
$specific = strtok($this->getInput('p'), '_');
|
||||
|
||||
if ($this->feedName) {
|
||||
$specific = $this->feedName;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'From search results':
|
||||
$specific = $this->getInput('s');
|
||||
@@ -102,26 +127,77 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
default: return parent::getName();
|
||||
}
|
||||
|
||||
return $specific . ' : Dailymotion Bridge';
|
||||
return $specific . ' : Dailymotion';
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
$uri = self::URI;
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
$uri .= 'user/' . urlencode($this->getInput('u')) . '/1';
|
||||
$uri .= 'user/' . urlencode($this->getInput('u'));
|
||||
break;
|
||||
case 'By playlist id':
|
||||
$uri .= 'playlist/' . urlencode(strtok($this->getInput('p'), '_'));
|
||||
break;
|
||||
case 'From search results':
|
||||
$uri .= 'search/' . urlencode($this->getInput('s'));
|
||||
if($this->getInput('pa')) {
|
||||
$uri .= '/' . $this->getInput('pa');
|
||||
|
||||
if(!is_null($this->getInput('pa'))) {
|
||||
$pa = $this->getInput('pa');
|
||||
|
||||
if ($this->getInput('pa') < 1) {
|
||||
$pa = 1;
|
||||
}
|
||||
|
||||
$uri .= '/' . $pa;
|
||||
}
|
||||
break;
|
||||
default: return parent::getURI();
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function getMetadata($id) {
|
||||
$metadata = array();
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . 'video/' . $id);
|
||||
|
||||
if(!$html) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$metadata['title'] = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
$metadata['timestamp'] = strtotime(
|
||||
$html->find('meta[property=video:release_date]', 0)->getAttribute('content')
|
||||
);
|
||||
$metadata['thumbnailUri'] = $html->find('meta[property=og:image]', 0)->getAttribute('content');
|
||||
$metadata['uri'] = $html->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
private function getPlaylistTitle($id) {
|
||||
$title = '';
|
||||
|
||||
$url = self::URI . 'playlist/' . $id;
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
|
||||
$title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
return $title;
|
||||
}
|
||||
|
||||
private function getApiUrl() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
return $this->apiUrl . '/user/' . $this->getInput('u')
|
||||
. '/videos?fields=' . urlencode($this->apiFields) . '&availability=1&sort=recent&limit=5';
|
||||
break;
|
||||
case 'By playlist id':
|
||||
return $this->apiUrl . '/playlist/' . $this->getInput('p')
|
||||
. '/videos?fields=' . urlencode($this->apiFields) . '&limit=5';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
defaultLinkTo($element, $this->getURI());
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['uri'] = html_entity_decode($element->find('a', 0)->href);
|
||||
$item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['timestamp'] = time();
|
||||
$thumbnailUri = $element->find('img', 0)->src;
|
||||
|
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);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
if (empty($item['content']))
|
||||
return null; //ignore dummy articles
|
||||
$article = getSimpleHTMLDOMCached($item['uri'])
|
||||
or returnServerError('Could not request Dark Reading: ' . $item['uri']);
|
||||
$item['content'] = $this->extractArticleContent($article);
|
||||
$item['enclosures'] = array(); //remove author profile picture
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractArticleContent($article){
|
||||
$content = $article->find('div#article-main', 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);
|
||||
}
|
||||
|
||||
$content = stripWithDelimiters($content, '<h1 ', '</h1>');
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
24
bridges/DaveRamseyBlogBridge.php
Normal file
24
bridges/DaveRamseyBlogBridge.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?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)
|
||||
or returnServerError('Could not request daveramsey.com.');
|
||||
|
||||
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
27
bridges/DavesTrailerPageBridge.php
Normal file
27
bridges/DavesTrailerPageBridge.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
class DavesTrailerPageBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'johnnygroovy';
|
||||
const NAME = 'Daves Trailer Page Bridge';
|
||||
const URI = 'https://www.davestrailerpage.co.uk/';
|
||||
const DESCRIPTION = 'Last trailers in HD thanks to Dave.';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(static::URI)
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
foreach ($html->find('tr[!align]') as $tr) {
|
||||
$item = array();
|
||||
|
||||
// title
|
||||
$item['title'] = $tr->find('td', 0)->find('b', 0)->plaintext;
|
||||
|
||||
// content
|
||||
$item['content'] = $tr->find('ul', 1);
|
||||
|
||||
// uri
|
||||
$item['uri'] = $tr->find('a', 3)->getAttribute('href');
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1145,7 +1145,7 @@ class PepperBridgeAbstract extends BridgeAbstract {
|
||||
} else {
|
||||
foreach ($list as $deal) {
|
||||
$item = array();
|
||||
$item['uri'] = $deal->find('div[class=threadGrid-title]', 0)->find('a', 0)->href;
|
||||
$item['uri'] = $deal->find('div[class*=threadGrid-title]', 0)->find('a', 0)->href;
|
||||
$item['title'] = $deal->find('a[class*=' . $selectorLink . ']', 0
|
||||
)->plaintext;
|
||||
$item['author'] = $deal->find('span.thread-username', 0)->plaintext;
|
||||
|
@@ -1,169 +0,0 @@
|
||||
<?php
|
||||
class DemonoidBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'metaMMA';
|
||||
const NAME = 'Demonoid';
|
||||
const URI = 'https://www.demonoid.pw/';
|
||||
const DESCRIPTION = 'Returns results from search';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Keywords' => array(
|
||||
'q' => array(
|
||||
'name' => 'keywords',
|
||||
'exampleValue' => 'keyword1 keyword2…',
|
||||
'required' => true,
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
),
|
||||
'Category Only' => array(
|
||||
'catOnly' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
),
|
||||
'User ID' => array(
|
||||
'userid' => array(
|
||||
'name' => 'user id',
|
||||
'exampleValue' => '00000',
|
||||
'required' => true,
|
||||
'type' => 'number'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(!empty($this->getInput('q'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?category=' .
|
||||
rawurlencode($this->getInput('category')) .
|
||||
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($this->getInput('q')) .
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} elseif(!empty($this->getInput('catOnly'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?uid=0&category=' .
|
||||
rawurlencode($this->getInput('catOnly')) .
|
||||
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} elseif(!empty($this->getInput('userid'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?uid=' .
|
||||
rawurlencode($this->getInput('userid')) .
|
||||
'&seeded=2'
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} else {
|
||||
returnServerError('Invalid parameters !');
|
||||
}
|
||||
|
||||
if(preg_match('~No torrents found~', $html)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $html->find('td[class=ctable_content_no_pad]', 0);
|
||||
$cursorCount = 4;
|
||||
$elementCount = 0;
|
||||
while($elementCount != 40) {
|
||||
$elementCount++;
|
||||
$currentElement = $table->find('tr', $cursorCount);
|
||||
if(preg_match('~items total~', $currentElement)) {
|
||||
break;
|
||||
}
|
||||
$item = array();
|
||||
//Do we have a date ?
|
||||
if(preg_match('~Added.*?(.*)~', $currentElement->plaintext, $dateStr)) {
|
||||
if(preg_match('~today~', $dateStr[0])) {
|
||||
date_default_timezone_set('UTC');
|
||||
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
|
||||
} else {
|
||||
preg_match('~(?<=ed on ).*\d+~', $currentElement->plaintext, $fullDateStr);
|
||||
date_default_timezone_set('UTC');
|
||||
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
|
||||
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
|
||||
}
|
||||
$cursorCount++;
|
||||
}
|
||||
|
||||
$content = $table->find('tr', $cursorCount)->find('a', 1);
|
||||
$cursorCount++;
|
||||
$torrentInfo = $table->find('tr', $cursorCount);
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['title'] = $content->plaintext;
|
||||
$item['id'] = self::URI . $content->href;
|
||||
$item['uri'] = self::URI . $content->href;
|
||||
$item['author'] = $torrentInfo->find('a[class=user]', 0)->plaintext;
|
||||
$item['seeders'] = $torrentInfo->find('font[class=green]', 0)->plaintext;
|
||||
$item['leechers'] = $torrentInfo->find('font[class=red]', 0)->plaintext;
|
||||
$item['size'] = $torrentInfo->find('td', 3)->plaintext;
|
||||
$item['content'] = 'Uploaded by ' . $item['author']
|
||||
. ' , Size ' . $item['size']
|
||||
. '<br>seeders: '
|
||||
. $item['seeders']
|
||||
. ' | leechers: '
|
||||
. $item['leechers']
|
||||
. '<br><a href="'
|
||||
. $item['id']
|
||||
. '">info page</a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
$cursorCount++;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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"
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -156,19 +162,23 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
|
||||
$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();
|
||||
|
||||
$item['uri'] = $article->find('[itemprop="name"]', 0)->href;
|
||||
$item['title'] = $article->find('[itemprop="name"]', 0)->title;
|
||||
$item['uri'] = $article->find('a', 0)->href;
|
||||
$item['title'] = $article->find('a[title]', 0)->title;
|
||||
|
||||
if($this->getInput('full')) {
|
||||
$item['content'] = $this->getFullNewsArticle($item['uri']);
|
||||
} else {
|
||||
$item['content'] = $article->find('[itemprop="description"]', 0)->plaintext;
|
||||
$item['content'] = $article->find('div.tile-body p', 0)->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if ($limit > 0 && count($this->items) >= $limit) break;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -45,29 +45,22 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$articles = $html->find('div[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 +68,6 @@ apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png'
|
||||
if($this->getInput('full')) {
|
||||
$fullArticle = $this->getFullArticle($item['uri']);
|
||||
$item['content'] = <<<EOD
|
||||
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
|
||||
<p>{$fullArticle}</p>
|
||||
EOD;
|
||||
} else {
|
||||
@@ -85,11 +77,21 @@ 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) {
|
||||
@@ -98,6 +100,10 @@ EOD;
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
if ($html->find('div.crayons-article__cover', 0)) {
|
||||
return $html->find('div.crayons-article__cover', 0) . $html->find('[id="article-body"]', 0);
|
||||
}
|
||||
|
||||
return $html->find('[id="article-body"]', 0);
|
||||
}
|
||||
}
|
||||
|
84
bridges/DiarioDeNoticiasBridge.php
Normal file
84
bridges/DiarioDeNoticiasBridge.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
class DiarioDeNoticiasBridge extends BridgeAbstract {
|
||||
const NAME = 'Diário de Notícias (PT)';
|
||||
const URI = 'https://dn.pt';
|
||||
const DESCRIPTION = 'Diário de Notícias (DN.PT)';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
'Tag' => array(
|
||||
'n' => array(
|
||||
'name' => 'Tag Name',
|
||||
'exampleValue' => 'rogerio-casanova',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const MONPT = array(
|
||||
'jan',
|
||||
'fev',
|
||||
'mar',
|
||||
'abr',
|
||||
'mai',
|
||||
'jun',
|
||||
'jul',
|
||||
'ago',
|
||||
'set',
|
||||
'out',
|
||||
'nov',
|
||||
'dez',
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.globalnoticias.pt/dn/common/images/favicons/favicon-128.png';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Tag':
|
||||
$name = self::NAME . ' | Tag | ' . $this->getInput('n');
|
||||
break;
|
||||
default:
|
||||
$name = self::NAME;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Tag':
|
||||
$url = self::URI . '/tag/' . $this->getInput('n') . '.html';
|
||||
break;
|
||||
default:
|
||||
$url = self::URI;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$archives = self::getURI();
|
||||
$html = getSimpleHTMLDOMCached($archives)
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach($html->find('article') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('.t-am-title', 0);
|
||||
$link = $element->find('a.t-am-text', 0);
|
||||
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['uri'] = self::URI . $link->href;
|
||||
|
||||
$snippet = $element->find('.t-am-lead', 0);
|
||||
if ($snippet) {
|
||||
$item['content'] = $snippet->plaintext;
|
||||
}
|
||||
preg_match('|edicao-do-dia\\/(?P<day>\d\d)-(?P<monpt>\w\w\w)-(?P<year>\d\d\d\d)|', $link->href, $d);
|
||||
if ($d) {
|
||||
$item['timestamp'] = sprintf('%s-%s-%s', $d['year'], array_search($d['monpt'], self::MONPT) + 1, $d['day']);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
60
bridges/DiarioDoAlentejoBridge.php
Normal file
60
bridges/DiarioDoAlentejoBridge.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?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')
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
require_once('Shimmie2Bridge.php');
|
||||
|
||||
class DollbooruBridge extends Shimmie2Bridge {
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Dollbooru';
|
||||
const URI = 'http://dollbooru.org/';
|
||||
const DESCRIPTION = 'Returns images from given page';
|
||||
}
|
123
bridges/DonnonsBridge.php
Normal file
123
bridges/DonnonsBridge.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?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)
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
$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();
|
||||
}
|
||||
}
|
6202
bridges/DownDetectorBridge.php
Normal file
6202
bridges/DownDetectorBridge.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -13,48 +13,51 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$json = $this->loadEmbeddedJsonData($html);
|
||||
|
||||
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, '[');
|
||||
@@ -91,6 +94,6 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
private function getFullSizeImagePath($preview_path){
|
||||
return str_replace('_1x', '', $preview_path);
|
||||
return explode('?compress=1', $preview_path)[0];
|
||||
}
|
||||
}
|
||||
|
@@ -40,7 +40,7 @@ class EconomistBridge extends BridgeAbstract {
|
||||
if ($nextprev)
|
||||
$nextprev->outertext = '';
|
||||
|
||||
$section = [ $article->find('h3[itemprop="articleSection"]', 0)->plaintext ];
|
||||
$section = array( $article->find('h3[itemprop="articleSection"]', 0)->plaintext );
|
||||
|
||||
$item = array();
|
||||
$item['title'] = $header->find('span', 0)->innertext . ': '
|
||||
|
@@ -47,5 +47,8 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
//Remove duplicates that sometimes show up on the website
|
||||
$this->items = array_unique($this->items, SORT_REGULAR);
|
||||
}
|
||||
}
|
||||
|
@@ -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) {
|
||||
@@ -120,9 +120,11 @@ class ElloBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function getAPIKey() {
|
||||
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
|
||||
$cacheFac = new CacheFactory();
|
||||
$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';
|
||||
|
||||
|
26
bridges/EngadgetBridge.php
Normal file
26
bridges/EngadgetBridge.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
class EngadgetBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'IceWreck';
|
||||
const NAME = 'Engadget Bridge';
|
||||
const URI = 'https://www.engadget.com/';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'Article content for Engadget.';
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas(static::URI . 'rss.xml', 15);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
// $articlePage gets the entire page's contents
|
||||
$articlePage = getSimpleHTMLDOM($newsItem->link);
|
||||
// figure contain's the main article image
|
||||
$article = $articlePage->find('figure', 0);
|
||||
// .article-text has the actual article
|
||||
foreach($articlePage->find('.article-text') as $element)
|
||||
$article = $article . $element;
|
||||
$item['content'] = $article;
|
||||
return $item;
|
||||
}
|
||||
}
|
93
bridges/EpicgamesBridge.php
Normal file
93
bridges/EpicgamesBridge.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
class EpicgamesBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Epic Games Store News';
|
||||
const MAINTAINER = 'otakuf';
|
||||
const URI = 'https://www.epicgames.com';
|
||||
const DESCRIPTION = 'Returns the latest posts from epicgames.com';
|
||||
const CACHE_TIMEOUT = 3600; // 60min
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 10,
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'العربية' => 'ar',
|
||||
'Deutsch' => 'de',
|
||||
'Español (Spain)' => 'es-ES',
|
||||
'Español (LA)' => 'es-MX',
|
||||
'Français' => 'fr',
|
||||
'Italiano' => 'it',
|
||||
'日本語' => 'ja',
|
||||
'한국어' => 'ko',
|
||||
'Polski' => 'pl',
|
||||
'Português (Brasil)' => 'pt-BR',
|
||||
'Русский' => 'ru',
|
||||
'ไทย' => 'th',
|
||||
'Türkçe' => 'tr',
|
||||
'简体中文' => 'zh-CN',
|
||||
'繁體中文' => 'zh-Hant',
|
||||
),
|
||||
'title' => 'Language of blog posts',
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
$api = 'https://store-content.ak.epicgames.com/api/';
|
||||
|
||||
// Get sticky posts first
|
||||
// Example: https://store-content.ak.epicgames.com/api/ru/content/blog/sticky?locale=ru
|
||||
$urlSticky = $api . $this->getInput('language') . '/content/blog/sticky';
|
||||
// Then get posts
|
||||
// Example: https://store-content.ak.epicgames.com/api/ru/content/blog?limit=25
|
||||
$urlBlog = $api . $this->getInput('language') . '/content/blog?limit=' . $this->getInput('postcount');
|
||||
|
||||
$dataSticky = getContents($urlSticky)
|
||||
or returnServerError('Unable to get the sticky posts from epicgames.com!');
|
||||
$dataBlog = getContents($urlBlog)
|
||||
or returnServerError('Unable to get the news posts from epicgames.com!');
|
||||
|
||||
// Merge data
|
||||
$decodedData = array_merge(json_decode($dataSticky), json_decode($dataBlog));
|
||||
|
||||
foreach($decodedData as $key => $value) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $value->url;
|
||||
$item['title'] = $value->title;
|
||||
$item['timestamp'] = $value->date;
|
||||
$item['author'] = 'Epic Games Store';
|
||||
if(!empty($value->author)) {
|
||||
$item['author'] = $value->author;
|
||||
}
|
||||
if(!empty($value->content)) {
|
||||
$item['content'] = defaultLinkTo($value->content, self::URI);
|
||||
}
|
||||
if(!empty($value->image)) {
|
||||
$item['enclosures'][] = $value->image;
|
||||
}
|
||||
$item['uid'] = $value->_id;
|
||||
$item['id'] = $value->_id;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
// Sort data
|
||||
usort($this->items, function ($item1, $item2) {
|
||||
if ($item2['timestamp'] == $item1['timestamp']) {
|
||||
return 0;
|
||||
}
|
||||
return ($item2['timestamp'] < $item1['timestamp']) ? -1 : 1;
|
||||
});
|
||||
|
||||
// Limit data
|
||||
$this->items = array_slice($this->items, 0, $this->getInput('postcount'));
|
||||
}
|
||||
}
|
70
bridges/EsquerdaNetBridge.php
Normal file
70
bridges/EsquerdaNetBridge.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?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)
|
||||
or returnServerError('Could not load content for ' . $uri);
|
||||
$content = $html->find('div#content div.content', 0);
|
||||
## Fix author
|
||||
$authorHTML = $html->find('.field-name-field-op-author a', 0);
|
||||
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;
|
||||
}
|
||||
}
|
@@ -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.ninja/';
|
||||
const DESCRIPTION = 'Suivi de série sur Extreme Download';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
|
@@ -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() {
|
||||
@@ -72,15 +77,15 @@ class FB2Bridge extends BridgeAbstract {
|
||||
$pageInfo = $this->getPageInfos($page, $cookies);
|
||||
|
||||
if($pageInfo['userId'] === null) {
|
||||
echo <<<EOD
|
||||
returnClientError(<<<EOD
|
||||
Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge
|
||||
EOD;
|
||||
die();
|
||||
EOD
|
||||
);
|
||||
} elseif($pageInfo['userId'] == -1) {
|
||||
echo <<<EOD
|
||||
returnClientError(<<<EOD
|
||||
This page is not accessible without being logged in.
|
||||
EOD;
|
||||
die();
|
||||
EOD
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,19 +100,19 @@ EOD;
|
||||
foreach($html->find('article') as $content) {
|
||||
|
||||
$item = array();
|
||||
//echo $content; die();
|
||||
|
||||
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
|
||||
if(isset($match[1]))
|
||||
$timestamp = $match[1];
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
67
bridges/FM4Bridge.php
Normal file
67
bridges/FM4Bridge.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
class FM4Bridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'joni1993';
|
||||
const NAME = 'FM4 Bridge';
|
||||
const URI = 'https://fm4.orf.at';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Feed for FM4 articles by tags (authors)';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'tag' => array(
|
||||
'name' => 'Tag (author, category, ...)',
|
||||
'title' => 'Tag to retrieve',
|
||||
'exampleValue' => 'musik'
|
||||
),
|
||||
'loadcontent' => array(
|
||||
'name' => 'Load Full Article Content',
|
||||
'title' => 'Retrieve full content of articles (may take longer)',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'pages' => array(
|
||||
'name' => 'Pages',
|
||||
'title' => 'Amount of pages to load',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 1
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function getPageData($tag, $page) {
|
||||
if($tag)
|
||||
$uri = self::URI . '/tags/' . $tag;
|
||||
else
|
||||
$uri = self::URI;
|
||||
|
||||
$uri = $uri . '?page=' . $page;
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$page_items = array();
|
||||
|
||||
foreach ($html->find('div[class*=listItem]') as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('a', 0)->href;
|
||||
$item['title'] = $article->find('h2', 0)->plaintext;
|
||||
$item['author'] = $article->find('p[class*=keyword]', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($article->find('p[class*=time]', 0)->plaintext);
|
||||
|
||||
if ($this->getInput('loadcontent')) {
|
||||
$item['content'] = getSimpleHTMLDOM($item['uri'])->find('div[class=storyText]', 0)->innertext
|
||||
or returnServerError('Error while downloading the full article');
|
||||
}
|
||||
|
||||
$page_items[] = $item;
|
||||
}
|
||||
return $page_items;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
for ($cur_page = 1; $cur_page <= $this->getInput('pages'); $cur_page++) {
|
||||
$this->items = array_merge($this->items, $this->getPageData($this->getInput('tag'), $cur_page));
|
||||
}
|
||||
}
|
||||
}
|
36
bridges/FabriceBellardBridge.php
Normal file
36
bridges/FabriceBellardBridge.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
class FabriceBellardBridge extends BridgeAbstract {
|
||||
const NAME = 'Fabrice Bellard';
|
||||
const URI = 'https://bellard.org/';
|
||||
const DESCRIPTION = "Fabrice Bellard's Home Page";
|
||||
const MAINTAINER = 'somini';
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach ($html->find('p') as $obj) {
|
||||
$item = array();
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$links = $obj->find('a');
|
||||
if (count($links) > 0) {
|
||||
$link_uri = $links[0]->href;
|
||||
} else {
|
||||
$link_uri = $this->getURI();
|
||||
}
|
||||
|
||||
/* try to make sure the link is valid */
|
||||
if ($link_uri[-1] !== '/' && strpos($link_uri, '/') === false) {
|
||||
$link_uri = $link_uri . '/';
|
||||
}
|
||||
|
||||
$item['title'] = strip_tags($obj->innertext);
|
||||
$item['uri'] = $link_uri;
|
||||
$item['content'] = $obj->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene, logmanoriginal';
|
||||
const NAME = 'Facebook Bridge';
|
||||
const NAME = 'Facebook Bridge | Main Site';
|
||||
const URI = 'https://www.facebook.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
|
||||
@@ -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;
|
||||
|
||||
@@ -142,9 +169,19 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
private function collectGroupData() {
|
||||
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
|
||||
if(getEnv('HTTP_ACCEPT_LANGUAGE')) {
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
|
||||
} else {
|
||||
$header = array();
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
$touchURI = str_replace(
|
||||
'https://www.facebook',
|
||||
'https://touch.facebook',
|
||||
$this->getURI()
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($touchURI, $header)
|
||||
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
||||
|
||||
if(!$this->isPublicGroup($html)) {
|
||||
@@ -155,19 +192,18 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
$this->groupName = $this->extractGroupName($html);
|
||||
|
||||
$posts = $html->find('div.userContentWrapper')
|
||||
$posts = $html->find('div.story_body_container')
|
||||
or returnServerError('Failed finding posts!');
|
||||
|
||||
foreach($posts as $post) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->extractGroupURI($post);
|
||||
$item['title'] = $this->extractGroupTitle($post);
|
||||
$item['author'] = $this->extractGroupAuthor($post);
|
||||
$item['content'] = $this->extractGroupContent($post);
|
||||
$item['timestamp'] = $this->extractGroupTimestamp($post);
|
||||
$item['enclosures'] = $this->extractGroupEnclosures($post);
|
||||
$item['uri'] = $this->extractGroupPostURI($post);
|
||||
$item['title'] = $this->extractGroupPostTitle($post);
|
||||
$item['author'] = $this->extractGroupPostAuthor($post);
|
||||
$item['content'] = $this->extractGroupPostContent($post);
|
||||
$item['enclosures'] = $this->extractGroupPostEnclosures($post);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
@@ -184,16 +220,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
$urlparts = parse_url($group);
|
||||
|
||||
if($urlparts['host'] !== parse_url(self::URI)['host']
|
||||
&& 'www.' . $urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $urlparts['host']
|
||||
. '", expected "'
|
||||
. parse_url(self::URI)['host']
|
||||
. '"!');
|
||||
|
||||
}
|
||||
$this->validateHost($urlparts['host']);
|
||||
|
||||
return explode('/', $urlparts['path'])[2];
|
||||
|
||||
@@ -205,24 +232,47 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
private function validateHost($provided_host) {
|
||||
// Handle mobile links
|
||||
if (strpos($provided_host, 'm.') === 0) {
|
||||
$provided_host = substr($provided_host, strlen('m.'));
|
||||
}
|
||||
if (strpos($provided_host, 'touch.') === 0) {
|
||||
$provided_host = substr($provided_host, strlen('touch.'));
|
||||
}
|
||||
|
||||
$facebook_host = parse_url(self::URI)['host'];
|
||||
|
||||
if ($provided_host !== $facebook_host
|
||||
&& 'www.' . $provided_host !== $facebook_host) {
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $provided_host
|
||||
. '", expected "'
|
||||
. $facebook_host
|
||||
. '"!');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $html simple_html_dom
|
||||
* @return bool
|
||||
*/
|
||||
private function isPublicGroup($html) {
|
||||
|
||||
// Facebook redirects to the groups about page for non-public groups
|
||||
$about = $html->find('#pagelet_group_about', 0);
|
||||
|
||||
return !($about);
|
||||
|
||||
// Facebook touch just presents a login page for non-public groups
|
||||
$title = $html->find('title', 0);
|
||||
return $title->plaintext !== 'Log in to Facebook | Facebook';
|
||||
}
|
||||
|
||||
private function extractGroupName($html) {
|
||||
|
||||
$ogtitle = $html->find('meta[property="og:title"]', 0)
|
||||
$ogtitle = $html->find('._de1', 0)
|
||||
or returnServerError('Unable to find group title!');
|
||||
|
||||
return html_entity_decode($ogtitle->content, ENT_QUOTES);
|
||||
return html_entity_decode($ogtitle->plaintext, ENT_QUOTES);
|
||||
}
|
||||
|
||||
private function extractGroupURI($post) {
|
||||
private function extractGroupPostURI($post) {
|
||||
|
||||
$elements = $post->find('a')
|
||||
or returnServerError('Unable to find URI!');
|
||||
@@ -231,7 +281,8 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
// Find the one that is a permalink
|
||||
if(strpos($anchor->href, 'permalink') !== false) {
|
||||
return $anchor->href;
|
||||
$arr = explode('?', $anchor->href, 2);
|
||||
return $arr[0];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -240,57 +291,61 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupContent($post) {
|
||||
private function extractGroupPostContent($post) {
|
||||
|
||||
$content = $post->find('div.userContent', 0)
|
||||
$content = $post->find('div._5rgt', 0)
|
||||
or returnServerError('Unable to find user content!');
|
||||
|
||||
return $content->innertext . $content->next_sibling()->innertext;
|
||||
$context_text = $content->innertext;
|
||||
if ($content->next_sibling() !== null) {
|
||||
$context_text .= $content->next_sibling()->innertext;
|
||||
}
|
||||
return $context_text;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupTimestamp($post) {
|
||||
private function extractGroupPostAuthor($post) {
|
||||
|
||||
$element = $post->find('abbr[data-utime]', 0)
|
||||
or returnServerError('Unable to find timestamp!');
|
||||
|
||||
return $element->getAttribute('data-utime');
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupAuthor($post) {
|
||||
|
||||
$element = $post->find('img', 0)
|
||||
$element = $post->find('h3 a', 0)
|
||||
or returnServerError('Unable to find author information!');
|
||||
|
||||
return $element->{'aria-label'};
|
||||
return $element->plaintext;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupEnclosures($post) {
|
||||
private function extractGroupPostEnclosures($post) {
|
||||
|
||||
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
|
||||
$elements = $post->find('span._6qdm');
|
||||
if ($post->find('div._5rgt', 0)->next_sibling() !== null) {
|
||||
array_push($elements, ...$post->find('div._5rgt', 0)->next_sibling()->find('i.img'));
|
||||
}
|
||||
|
||||
$enclosures = array();
|
||||
|
||||
$background_img_regex = '/background-image: ?url\\((.+?)\\);/';
|
||||
|
||||
foreach($elements as $enclosure) {
|
||||
$enclosures[] = $enclosure->src;
|
||||
if(preg_match($background_img_regex, $enclosure, $matches) > 0) {
|
||||
$bg_img_value = trim(html_entity_decode($matches[1], ENT_QUOTES), "'\"");
|
||||
$bg_img_url = urldecode(preg_replace('/\\\([0-9a-z]{2}) /', '%$1', $bg_img_value));
|
||||
$enclosures[] = urldecode($bg_img_url);
|
||||
}
|
||||
}
|
||||
|
||||
return empty($enclosures) ? null : $enclosures;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupTitle($post) {
|
||||
private function extractGroupPostTitle($post) {
|
||||
|
||||
$element = $post->find('h5', 0)
|
||||
$element = $post->find('h3', 0)
|
||||
or returnServerError('Unable to find title!');
|
||||
|
||||
if(strpos($element->plaintext, 'shared') === false) {
|
||||
|
||||
$content = strip_tags($this->extractGroupContent($post));
|
||||
$content = strip_tags($this->extractGroupPostContent($post));
|
||||
|
||||
return $this->extractGroupAuthor($post)
|
||||
return $this->extractGroupPostAuthor($post)
|
||||
. ' posted: '
|
||||
. substr(
|
||||
$content,
|
||||
@@ -317,13 +372,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
$urlparts = parse_url($user);
|
||||
|
||||
if($urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $urlparts['host']
|
||||
. '", expected "'
|
||||
. parse_url(self::URI)['host']
|
||||
. '"!');
|
||||
}
|
||||
$this->validateHost($urlparts['host']);
|
||||
|
||||
if(!array_key_exists('path', $urlparts)
|
||||
|| $urlparts['path'] === '/') {
|
||||
@@ -505,7 +554,11 @@ EOD;
|
||||
// Retrieve page contents
|
||||
if(is_null($html)) {
|
||||
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
|
||||
if(getEnv('HTTP_ACCEPT_LANGUAGE')) {
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
|
||||
} else {
|
||||
$header = array();
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('No results for this query.');
|
||||
@@ -520,7 +573,7 @@ EOD;
|
||||
}
|
||||
|
||||
// No captcha? We can carry on retrieving page contents :)
|
||||
// First, we check wether the page is public or not
|
||||
// First, we check whether the page is public or not
|
||||
$loginForm = $html->find('._585r', 0);
|
||||
|
||||
if($loginForm != null) {
|
||||
@@ -580,6 +633,8 @@ EOD;
|
||||
'._5mly', // Remove embedded videos (the preview image remains)
|
||||
'._2ezg', // Remove "Views ..."
|
||||
'.hidden_elem', // Remove hidden elements (they are hidden anyway)
|
||||
'.timestampContent', // Remove relative timestamp
|
||||
'._6spk', // Remove redundant separator
|
||||
);
|
||||
|
||||
foreach($content_filters as $filter) {
|
||||
@@ -664,8 +719,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
|
||||
@@ -685,6 +747,7 @@ EOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion (User)
|
||||
|
||||
}
|
||||
|
185
bridges/FicbookBridge.php
Normal file
185
bridges/FicbookBridge.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
class FicbookBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Ficbook Bridge';
|
||||
const URI = 'https://ficbook.net/';
|
||||
const DESCRIPTION = 'No description provided';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Site News' => array(),
|
||||
'Fiction Updates' => array(
|
||||
'fiction_id' => array(
|
||||
'name' => 'Fanfiction ID',
|
||||
'type' => 'text',
|
||||
'pattern' => '[0-9]+',
|
||||
'required' => true,
|
||||
'title' => 'Insert fanfiction ID',
|
||||
'exampleValue' => '5783919',
|
||||
),
|
||||
'include_contents' => array(
|
||||
'name' => 'Include contents',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to include contents in the feed',
|
||||
),
|
||||
),
|
||||
'Fiction Comments' => array(
|
||||
'fiction_id' => array(
|
||||
'name' => 'Fanfiction ID',
|
||||
'type' => 'text',
|
||||
'pattern' => '[0-9]+',
|
||||
'required' => true,
|
||||
'title' => 'Insert fanfiction ID',
|
||||
'exampleValue' => '5783919',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
protected $titleName;
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Site News': {
|
||||
// For some reason this is not HTTPS
|
||||
return 'http://ficbook.net/sitenews';
|
||||
}
|
||||
case 'Fiction Updates': {
|
||||
return self::URI
|
||||
. 'readfic/'
|
||||
. urlencode($this->getInput('fiction_id'));
|
||||
}
|
||||
case 'Fiction Comments': {
|
||||
return self::URI
|
||||
. 'readfic/'
|
||||
. urlencode($this->getInput('fiction_id'))
|
||||
. '/comments#content';
|
||||
}
|
||||
default: return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
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 = 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);
|
||||
case 'Fiction Comments': return $this->collectCommentsData($html);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function collectSiteNews($html) {
|
||||
foreach($html->find('.news_view') as $news) {
|
||||
$this->items[] = array(
|
||||
'title' => $news->find('h1.title', 0)->plaintext,
|
||||
'timestamp' => strtotime($this->fixDate($news->find('span[title]', 0)->title)),
|
||||
'content' => $news->find('.news_text', 0),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectCommentsData($html) {
|
||||
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,
|
||||
'author' => $article->find('.comment_author', 0)->plaintext,
|
||||
'timestamp' => strtotime($this->fixDate($article->find('time[datetime]', 0)->datetime)),
|
||||
'content' => $article->find('.comment_message', 0),
|
||||
'enclosures' => array($article->find('img', 0)->src),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectUpdatesData($html) {
|
||||
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,
|
||||
'timestamp' => strtotime($this->fixDate($chapter->find('span[title]', 0)->title)),
|
||||
);
|
||||
|
||||
if($this->getInput('include_contents')) {
|
||||
$content = getSimpleHTMLDOMCached($item['uri']);
|
||||
$item['content'] = $content->find('#content', 0);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
// Sort by time, descending
|
||||
usort($this->items, function($a, $b){ return $b['timestamp'] - $a['timestamp']; });
|
||||
}
|
||||
}
|
||||
|
||||
private function fixDate($date) {
|
||||
|
||||
// FIXME: This list was generated using Google tranlator. Someone who
|
||||
// actually knows russian should check this list! Please keep in mind
|
||||
// that month names must match exactly the names returned by Ficbook.
|
||||
$ru_month = array(
|
||||
'января',
|
||||
'февраля',
|
||||
'марта',
|
||||
'апреля',
|
||||
'мая',
|
||||
'июня',
|
||||
'июля',
|
||||
'августа',
|
||||
'сентября',
|
||||
'октября',
|
||||
'ноября',
|
||||
'декабря',
|
||||
);
|
||||
|
||||
$en_month = array(
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December',
|
||||
);
|
||||
|
||||
$fixed_date = str_replace($ru_month, $en_month, $date);
|
||||
|
||||
if($fixed_date === $date) {
|
||||
Debug::log('Unable to fix date: ' . $date);
|
||||
return null;
|
||||
}
|
||||
|
||||
return $fixed_date;
|
||||
|
||||
}
|
||||
}
|
@@ -62,11 +62,16 @@ class FindACrewBridge extends BridgeAbstract {
|
||||
foreach ($annonces as $annonce) {
|
||||
$item = array();
|
||||
|
||||
$img = parent::getURI() . $annonce->find('.lst-pic img', 0)->getAttribute('src');
|
||||
$link = parent::getURI() . $annonce->find('.lst-ctrls a', 0)->href;
|
||||
$htmlDetail = getSimpleHTMLDOMCached($link . '?mdl=2'); // add ?mdl=2 for xhr content not full html page
|
||||
|
||||
$img = parent::getURI() . $htmlDetail->find('img.img-responsive', 0)->getAttribute('src');
|
||||
$item['title'] = $annonce->find('.lst-tags span', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $annonce->find('.lst-ctrls a', 0)->href;
|
||||
$content = $annonce->find('.lst-dtl', 0)->innertext;
|
||||
$item['content'] = "<img src='$img' /><br>$content";
|
||||
$item['uri'] = $link;
|
||||
$content = $htmlDetail->find('.panel-body div.clearfix.row > div', 1)->innertext;
|
||||
$content .= $htmlDetail->find('.panel-body > div', 1)->innertext;
|
||||
$content = defaultLinkTo($content, parent::getURI());
|
||||
$item['content'] = $content;
|
||||
$item['enclosures'] = array($img);
|
||||
$item['categories'] = array($annonce->find('.css_AccLocCur', 0)->plaintext);
|
||||
$this->items[] = $item;
|
||||
|
50
bridges/FirstLookMediaTechBridge.php
Normal file
50
bridges/FirstLookMediaTechBridge.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
class FirstLookMediaTechBridge extends BridgeAbstract {
|
||||
const NAME = 'First Look Media - Technology';
|
||||
const URI = 'https://tech.firstlook.media';
|
||||
const DESCRIPTION = 'First Look Media Technology page';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'projects' => array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Include Projects?',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
if ($this->getInput('projects')) {
|
||||
$top_projects = $html->find('.PromoList-ul', 0);
|
||||
foreach($top_projects->find('li.PromoList-item') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item_uri = $element->find('a', 0);
|
||||
$item['uri'] = $item_uri->href;
|
||||
$item['title'] = strip_tags($item_uri->innertext);
|
||||
$item['content'] = $element->find('div > div', 0);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
$top_articles = $html->find('.PromoList-ul', 1);
|
||||
foreach($top_articles->find('li.PromoList-item') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item_left = $element->find('div > div', 0);
|
||||
$item_date = $element->find('.PromoList-date', 0);
|
||||
$item['timestamp'] = strtotime($item_date->innertext);
|
||||
$item_date->outertext = ''; /* Remove */
|
||||
$item['author'] = $item_left->innertext;
|
||||
$item_uri = $element->find('a', 0);
|
||||
$item['uri'] = self::URI . $item_uri->href;
|
||||
$item['title'] = strip_tags($item_uri);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
52
bridges/FolhaDeSaoPauloBridge.php
Normal file
52
bridges/FolhaDeSaoPauloBridge.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
class FolhaDeSaoPauloBridge extends FeedExpander {
|
||||
const MAINTAINER = 'somini';
|
||||
const NAME = 'Folha de São Paulo';
|
||||
const URI = 'https://www1.folha.uol.com.br';
|
||||
const DESCRIPTION = 'Returns the newest posts from Folha de São Paulo (full text)';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'feed' => array(
|
||||
'name' => 'Feed sub-URL',
|
||||
'type' => 'text',
|
||||
'title' => 'Select the sub-feed (see https://www1.folha.uol.com.br/feed/)',
|
||||
'exampleValue' => 'emcimadahora/rss091.xml',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$articleHTMLContent = getSimpleHTMLDOMCached($item['uri']);
|
||||
if($articleHTMLContent) {
|
||||
foreach ($articleHTMLContent->find('div.c-news__body .is-hidden') as $toRemove) {
|
||||
$toRemove->innertext = '';
|
||||
}
|
||||
$item_content = $articleHTMLContent->find('div.c-news__body', 0);
|
||||
if ($item_content) {
|
||||
$text = $item_content->innertext;
|
||||
$text = strip_tags($text, '<p><b><a><blockquote><figure><figcaption><img><strong><em>');
|
||||
$item['content'] = $text;
|
||||
$item['uri'] = explode('*', $item['uri'])[1];
|
||||
}
|
||||
} else {
|
||||
Debug::log('???: ' . $item['uri']);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$feed_input = $this->getInput('feed');
|
||||
if (substr($feed_input, 0, strlen(self::URI)) === self::URI) {
|
||||
Debug::log('Input:: ' . $feed_input);
|
||||
$feed_url = $feed_input;
|
||||
} else {
|
||||
/* TODO: prepend `/` if missing */
|
||||
$feed_url = self::URI . '/' . $this->getInput('feed');
|
||||
}
|
||||
Debug::log('URL: ' . $feed_url);
|
||||
$this->collectExpandableDatas($feed_url);
|
||||
}
|
||||
}
|
27
bridges/FreeCodeCampBridge.php
Normal file
27
bridges/FreeCodeCampBridge.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
class FreeCodeCampBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'IceWreck';
|
||||
const NAME = 'FreeCodecamp Bridge';
|
||||
const URI = 'https://www.freecodecamp.org';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'RSS feed for FreeCodeCamp';
|
||||
// Freecodecamp removed their old full content rss feed and replaced it with one liner content.
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('https://www.freecodecamp.org/news/rss/', 15);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
// $articlePage gets the entire page's contents
|
||||
$articlePage = getSimpleHTMLDOM($newsItem->link);
|
||||
// figure contain's the main article image
|
||||
$article = $articlePage->find('figure', 0);
|
||||
// the actual article
|
||||
foreach($articlePage->find('.post-full-content') as $element)
|
||||
$article = $article . $element;
|
||||
$item['content'] = $article;
|
||||
return $item;
|
||||
}
|
||||
}
|
918
bridges/FurAffinityBridge.php
Normal file
918
bridges/FurAffinityBridge.php
Normal file
@@ -0,0 +1,918 @@
|
||||
<?php
|
||||
class FurAffinityBridge extends BridgeAbstract {
|
||||
const NAME = 'FurAffinity Bridge';
|
||||
const URI = 'https://www.furaffinity.net';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Returns posts from various sections of FurAffinity';
|
||||
const MAINTAINER = 'Roliga';
|
||||
const PARAMETERS = array(
|
||||
'Search' => array(
|
||||
'q' => array(
|
||||
'name' => 'Query',
|
||||
'required' => true
|
||||
),
|
||||
'rating-general' => array(
|
||||
'name' => 'General',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'rating-mature' => array(
|
||||
'name' => 'Mature',
|
||||
'type' => 'checkbox',
|
||||
),
|
||||
'rating-adult' => array(
|
||||
'name' => 'Adult',
|
||||
'type' => 'checkbox',
|
||||
),
|
||||
'range' => array(
|
||||
'name' => 'Time range',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'A Day' => 'day',
|
||||
'3 Days' => '3days',
|
||||
'A Week' => 'week',
|
||||
'A Month' => 'month',
|
||||
'All time' => 'all'
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
),
|
||||
'type-art' => array(
|
||||
'name' => 'Art',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'type-flash' => array(
|
||||
'name' => 'Flash',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'type-photo' => array(
|
||||
'name' => 'Photography',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'type-music' => array(
|
||||
'name' => 'Music',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'type-story' => array(
|
||||
'name' => 'Story',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'type-poetry' => array(
|
||||
'name' => 'Poetry',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'mode' => array(
|
||||
'name' => 'Match mode',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All of the words' => 'all',
|
||||
'Any of the words' => 'any',
|
||||
'Extended' => 'extended'
|
||||
),
|
||||
'defaultValue' => 'extended'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full view',
|
||||
'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'cache' => array(
|
||||
'name' => 'Cache submission pages',
|
||||
'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
),
|
||||
'Browse' => array(
|
||||
'cat' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Visual Art' => array(
|
||||
'All' => 1,
|
||||
'Artwork (Digital)' => 2,
|
||||
'Artwork (Traditional)' => 3,
|
||||
'Cellshading' => 4,
|
||||
'Crafting' => 5,
|
||||
'Designs' => 6,
|
||||
'Flash' => 7,
|
||||
'Fursuiting' => 8,
|
||||
'Icons' => 9,
|
||||
'Mosaics' => 10,
|
||||
'Photography' => 11,
|
||||
'Sculpting' => 12
|
||||
),
|
||||
'Readable Art' => array(
|
||||
'Story' => 13,
|
||||
'Poetry' => 14,
|
||||
'Prose' => 15
|
||||
),
|
||||
'Audio Art' => array(
|
||||
'Music' => 16,
|
||||
'Podcasts' => 17
|
||||
),
|
||||
'Downloadable' => array(
|
||||
'Skins' => 18,
|
||||
'Handhelds' => 19,
|
||||
'Resources' => 20
|
||||
),
|
||||
'Other Stuff' => array(
|
||||
'Adoptables' => 21,
|
||||
'Auctions' => 22,
|
||||
'Contests' => 23,
|
||||
'Current Events' => 24,
|
||||
'Desktops' => 25,
|
||||
'Stockart' => 26,
|
||||
'Screenshots' => 27,
|
||||
'Scraps' => 28,
|
||||
'Wallpaper' => 29,
|
||||
'YCH / Sale' => 30,
|
||||
'Other' => 31
|
||||
)
|
||||
),
|
||||
'defaultValue' => 1
|
||||
),
|
||||
'atype' => array(
|
||||
'name' => 'Type',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'General Things' => array(
|
||||
'All' => 1,
|
||||
'Abstract' => 2,
|
||||
'Animal related (non-anthro)' => 3,
|
||||
'Anime' => 4,
|
||||
'Comics' => 5,
|
||||
'Doodle' => 6,
|
||||
'Fanart' => 7,
|
||||
'Fantasy' => 8,
|
||||
'Human' => 9,
|
||||
'Portraits' => 10,
|
||||
'Scenery' => 11,
|
||||
'Still Life' => 12,
|
||||
'Tutorials' => 13,
|
||||
'Miscellaneous' => 14
|
||||
),
|
||||
'Fetish / Furry specialty' => array(
|
||||
'Baby fur' => 101,
|
||||
'Bondage' => 102,
|
||||
'Digimon' => 103,
|
||||
'Fat Furs' => 104,
|
||||
'Fetish Other' => 105,
|
||||
'Fursuit' => 106,
|
||||
'Gore / Macabre Art' => 119,
|
||||
'Hyper' => 107,
|
||||
'Inflation' => 108,
|
||||
'Macro / Micro' => 109,
|
||||
'Muscle' => 110,
|
||||
'My Little Pony / Brony' => 111,
|
||||
'Paw' => 112,
|
||||
'Pokemon' => 113,
|
||||
'Pregnancy' => 114,
|
||||
'Sonic' => 115,
|
||||
'Transformation' => 116,
|
||||
'Vore' => 117,
|
||||
'Water Sports' => 118,
|
||||
'General Furry Art' => 100
|
||||
),
|
||||
'Music' => array(
|
||||
'Techno' => 201,
|
||||
'Trance' => 202,
|
||||
'House' => 203,
|
||||
'90s' => 204,
|
||||
'80s' => 205,
|
||||
'70s' => 206,
|
||||
'60s' => 207,
|
||||
'Pre-60s' => 208,
|
||||
'Classical' => 209,
|
||||
'Game Music' => 210,
|
||||
'Rock' => 211,
|
||||
'Pop' => 212,
|
||||
'Rap' => 213,
|
||||
'Industrial' => 214,
|
||||
'Other Music' => 200
|
||||
)
|
||||
),
|
||||
'defaultValue' => 1
|
||||
),
|
||||
'species' => array(
|
||||
'name' => 'Species',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Unspecified / Any' => 1,
|
||||
'Amphibian' => array(
|
||||
'Frog' => 1001,
|
||||
'Newt' => 1002,
|
||||
'Salamander' => 1003,
|
||||
'Amphibian (Other)' => 1000
|
||||
),
|
||||
'Aquatic' => array(
|
||||
'Cephalopod' => 2001,
|
||||
'Dolphin' => 2002,
|
||||
'Fish' => 2005,
|
||||
'Porpoise' => 2004,
|
||||
'Seal' => 6068,
|
||||
'Shark' => 2006,
|
||||
'Whale' => 2003,
|
||||
'Aquatic (Other)' => 2000
|
||||
),
|
||||
'Avian' => array(
|
||||
'Corvid' => 3001,
|
||||
'Crow' => 3002,
|
||||
'Duck' => 3003,
|
||||
'Eagle' => 3004,
|
||||
'Falcon' => 3005,
|
||||
'Goose' => 3006,
|
||||
'Gryphon' => 3007,
|
||||
'Hawk' => 3008,
|
||||
'Owl' => 3009,
|
||||
'Phoenix' => 3010,
|
||||
'Swan' => 3011,
|
||||
'Avian (Other)' => 3000
|
||||
),
|
||||
'Bears & Ursines' => array(
|
||||
'Bear' => 6002
|
||||
),
|
||||
'Camelids' => array(
|
||||
'Camel' => 6074,
|
||||
'Llama' => 6036
|
||||
),
|
||||
'Canines & Lupines' => array(
|
||||
'Coyote' => 6008,
|
||||
'Doberman' => 6009,
|
||||
'Dog' => 6010,
|
||||
'Dingo' => 6011,
|
||||
'German Shepherd' => 6012,
|
||||
'Jackal' => 6013,
|
||||
'Husky' => 6014,
|
||||
'Wolf' => 6016,
|
||||
'Canine (Other)' => 6017
|
||||
),
|
||||
'Cervines' => array(
|
||||
'Cervine (Other)' => 6018
|
||||
),
|
||||
'Cows & Bovines' => array(
|
||||
'Antelope' => 6004,
|
||||
'Cows' => 6003,
|
||||
'Gazelle' => 6005,
|
||||
'Goat' => 6006,
|
||||
'Bovines (General)' => 6007
|
||||
),
|
||||
'Dragons' => array(
|
||||
'Eastern Dragon' => 4001,
|
||||
'Hydra' => 4002,
|
||||
'Serpent' => 4003,
|
||||
'Western Dragon' => 4004,
|
||||
'Wyvern' => 4005,
|
||||
'Dragon (Other)' => 4000
|
||||
),
|
||||
'Equestrians' => array(
|
||||
'Donkey' => 6019,
|
||||
'Horse' => 6034,
|
||||
'Pony' => 6073,
|
||||
'Zebra' => 6071
|
||||
),
|
||||
'Exotic & Mythicals' => array(
|
||||
'Argonian' => 5002,
|
||||
'Chakat' => 5003,
|
||||
'Chocobo' => 5004,
|
||||
'Citra' => 5005,
|
||||
'Crux' => 5006,
|
||||
'Daemon' => 5007,
|
||||
'Digimon' => 5008,
|
||||
'Dracat' => 5009,
|
||||
'Draenei' => 5010,
|
||||
'Elf' => 5011,
|
||||
'Gargoyle' => 5012,
|
||||
'Iksar' => 5013,
|
||||
'Kaiju/Monster' => 5015,
|
||||
'Langurhali' => 5014,
|
||||
'Moogle' => 5017,
|
||||
'Naga' => 5016,
|
||||
'Orc' => 5018,
|
||||
'Pokemon' => 5019,
|
||||
'Satyr' => 5020,
|
||||
'Sergal' => 5021,
|
||||
'Tanuki' => 5022,
|
||||
'Unicorn' => 5023,
|
||||
'Xenomorph' => 5024,
|
||||
'Alien (Other)' => 5001,
|
||||
'Exotic (Other)' => 5000
|
||||
),
|
||||
'Felines' => array(
|
||||
'Domestic Cat' => 6020,
|
||||
'Cheetah' => 6021,
|
||||
'Cougar' => 6022,
|
||||
'Jaguar' => 6023,
|
||||
'Leopard' => 6024,
|
||||
'Lion' => 6025,
|
||||
'Lynx' => 6026,
|
||||
'Ocelot' => 6027,
|
||||
'Panther' => 6028,
|
||||
'Tiger' => 6029,
|
||||
'Feline (Other)' => 6030
|
||||
),
|
||||
'Insects' => array(
|
||||
'Arachnid' => 8000,
|
||||
'Mantid' => 8004,
|
||||
'Scorpion' => 8005,
|
||||
'Insect (Other)' => 8003
|
||||
),
|
||||
'Mammals (Other)' => array(
|
||||
'Bat' => 6001,
|
||||
'Giraffe' => 6031,
|
||||
'Hedgehog' => 6032,
|
||||
'Hippopotamus' => 6033,
|
||||
'Hyena' => 6035,
|
||||
'Panda' => 6052,
|
||||
'Pig/Swine' => 6053,
|
||||
'Rabbit/Hare' => 6059,
|
||||
'Raccoon' => 6060,
|
||||
'Red Panda' => 6062,
|
||||
'Meerkat' => 6043,
|
||||
'Mongoose' => 6044,
|
||||
'Rhinoceros' => 6063,
|
||||
'Mammals (Other)' => 6000
|
||||
),
|
||||
'Marsupials' => array(
|
||||
'Opossum' => 6037,
|
||||
'Kangaroo' => 6038,
|
||||
'Koala' => 6039,
|
||||
'Quoll' => 6040,
|
||||
'Wallaby' => 6041,
|
||||
'Marsupial (Other)' => 6042
|
||||
),
|
||||
'Mustelids' => array(
|
||||
'Badger' => 6045,
|
||||
'Ferret' => 6046,
|
||||
'Mink' => 6048,
|
||||
'Otter' => 6047,
|
||||
'Skunk' => 6069,
|
||||
'Weasel' => 6049,
|
||||
'Mustelid (Other)' => 6051
|
||||
),
|
||||
'Primates' => array(
|
||||
'Gorilla' => 6054,
|
||||
'Human' => 6055,
|
||||
'Lemur' => 6056,
|
||||
'Monkey' => 6057,
|
||||
'Primate (Other)' => 6058
|
||||
),
|
||||
'Reptillian' => array(
|
||||
'Alligator & Crocodile' => 7001,
|
||||
'Gecko' => 7003,
|
||||
'Iguana' => 7004,
|
||||
'Lizard' => 7005,
|
||||
'Snakes & Serpents' => 7006,
|
||||
'Turtle' => 7007,
|
||||
'Reptilian (Other)' => 7000
|
||||
),
|
||||
'Rodents' => array(
|
||||
'Beaver' => 6064,
|
||||
'Mouse' => 6065,
|
||||
'Rat' => 6061,
|
||||
'Squirrel' => 6070,
|
||||
'Rodent (Other)' => 6067
|
||||
),
|
||||
'Vulpines' => array(
|
||||
'Fennec' => 6072,
|
||||
'Fox' => 6075,
|
||||
'Vulpine (Other)' => 6015
|
||||
),
|
||||
'Other' => array(
|
||||
'Dinosaur' => 8001,
|
||||
'Wolverine' => 6050
|
||||
)
|
||||
),
|
||||
'defaultValue' => 1
|
||||
),
|
||||
'gender' => array(
|
||||
'name' => 'Gender',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Any' => 0,
|
||||
'Male' => 2,
|
||||
'Female' => 3,
|
||||
'Herm' => 4,
|
||||
'Transgender' => 5,
|
||||
'Multiple characters' => 6,
|
||||
'Other / Not Specified' => 7
|
||||
),
|
||||
'defaultValue' => 0
|
||||
),
|
||||
'rating_general' => array(
|
||||
'name' => 'General',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'rating_mature' => array(
|
||||
'name' => 'Mature',
|
||||
'type' => 'checkbox',
|
||||
),
|
||||
'rating_adult' => array(
|
||||
'name' => 'Adult',
|
||||
'type' => 'checkbox',
|
||||
),
|
||||
'limit-browse' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full view',
|
||||
'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'cache' => array(
|
||||
'name' => 'Cache submission pages',
|
||||
'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
|
||||
),
|
||||
'Journals' => array(
|
||||
'username-journals' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => -1,
|
||||
'title' => 'Limit number of journals to return. -1 for unlimited.'
|
||||
)
|
||||
|
||||
),
|
||||
'Single Journal' => array(
|
||||
'journal-id' => array(
|
||||
'name' => 'Journal ID',
|
||||
'required' => true,
|
||||
'type' => 'number',
|
||||
'title' => 'Number seen in journal URL'
|
||||
)
|
||||
),
|
||||
'Gallery' => array(
|
||||
'username-gallery' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full view',
|
||||
'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'cache' => array(
|
||||
'name' => 'Cache submission pages',
|
||||
'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
),
|
||||
'Scraps' => array(
|
||||
'username-scraps' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full view',
|
||||
'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'cache' => array(
|
||||
'name' => 'Cache submission pages',
|
||||
'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
),
|
||||
'Favorites' => array(
|
||||
'username-favorites' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full view',
|
||||
'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'cache' => array(
|
||||
'name' => 'Cache submission pages',
|
||||
'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
),
|
||||
'Gallery Folder' => array(
|
||||
'username-folder' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'folder-id' => array(
|
||||
'name' => 'Folder ID',
|
||||
'required' => true,
|
||||
'type' => 'number',
|
||||
'title' => 'Number seen in folder URL'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full view',
|
||||
'title' => 'Include description, tags, date and larger image in article. Uses more bandwidth.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'cache' => array(
|
||||
'name' => 'Cache submission pages',
|
||||
'title' => 'Reduces requests to FA when Full view is enabled. Changes to submission details may be delayed.',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* This was aquired by creating a new user on FA then
|
||||
* extracting the cookie from the browsers dev console.
|
||||
*/
|
||||
const FA_AUTH_COOKIE = 'b=4ce65691-b50f-4742-a990-bf28d6de16ee; a=ca6e4566-9d81-4263-9444-653b142e35f8';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// Single journal
|
||||
$regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/journal\/(\d+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['journal-id'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Journals
|
||||
$regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/journals\/([^\/&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['username-journals'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Gallery folder
|
||||
$regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/gallery\/([^\/&?\n]+)\/folder\/(\d+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['username-folder'] = urldecode($matches[3]);
|
||||
$params['folder-id'] = urldecode($matches[4]);
|
||||
$params['full'] = 'on';
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Gallery (must be after gallery folder)
|
||||
$regex = '/^(https?:\/\/)?(www\.)?furaffinity.net\/(gallery|scraps|favorites)\/([^\/&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['username-' . $matches[3]] = urldecode($matches[4]);
|
||||
$params['full'] = 'on';
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Search':
|
||||
return 'Search For '
|
||||
. $this->getInput('q');
|
||||
case 'Browse':
|
||||
return 'Browse';
|
||||
case 'Journals':
|
||||
return $this->getInput('username-journals');
|
||||
case 'Single Journal':
|
||||
return 'Journal '
|
||||
. $this->getInput('journal-id');
|
||||
case 'Gallery':
|
||||
return $this->getInput('username-gallery');
|
||||
case 'Scraps':
|
||||
return $this->getInput('username-scraps');
|
||||
case 'Favorites':
|
||||
return $this->getInput('username-favorites');
|
||||
case 'Gallery Folder':
|
||||
return $this->getInput('username-folder')
|
||||
. '\'s Folder '
|
||||
. $this->getInput('folder-id');
|
||||
default: return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Search':
|
||||
return 'FurAffinity Search For '
|
||||
. $this->getInput('q');
|
||||
case 'Browse':
|
||||
return 'FurAffinity Browse';
|
||||
case 'Journals':
|
||||
return 'FurAffinity Journals By '
|
||||
. $this->getInput('username-journals');
|
||||
case 'Single Journal':
|
||||
return 'FurAffinity Journal '
|
||||
. $this->getInput('journal-id');
|
||||
case 'Gallery':
|
||||
return 'FurAffinity Gallery By '
|
||||
. $this->getInput('username-gallery');
|
||||
case 'Scraps':
|
||||
return 'FurAffinity Scraps By '
|
||||
. $this->getInput('username-scraps');
|
||||
case 'Favorites':
|
||||
return 'FurAffinity Favorites By '
|
||||
. $this->getInput('username-favorites');
|
||||
case 'Gallery Folder':
|
||||
return 'FurAffinity Gallery Folder '
|
||||
. $this->getInput('folder-id')
|
||||
. ' By '
|
||||
. $this->getInput('username-folder');
|
||||
default: return parent::getDescription();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Search':
|
||||
return SELF::URI
|
||||
. '/search';
|
||||
case 'Browse':
|
||||
return SELF::URI
|
||||
. '/browse';
|
||||
case 'Journals':
|
||||
return SELF::URI
|
||||
. '/journals/'
|
||||
. $this->getInput('username-journals');
|
||||
case 'Single Journal':
|
||||
return SELF::URI
|
||||
. '/journal/'
|
||||
. $this->getInput('journal-id');
|
||||
case 'Gallery':
|
||||
return SELF::URI
|
||||
. '/gallery/'
|
||||
. $this->getInput('username-gallery');
|
||||
case 'Scraps':
|
||||
return SELF::URI
|
||||
. '/scraps/'
|
||||
. $this->getInput('username-scraps');
|
||||
case 'Favorites':
|
||||
return SELF::URI
|
||||
. '/favorites/'
|
||||
. $this->getInput('username-favorites');
|
||||
case 'Gallery Folder':
|
||||
return SELF::URI
|
||||
. '/gallery/'
|
||||
. $this->getInput('username-folder')
|
||||
. '/folder/'
|
||||
. $this->getInput('folder-id');
|
||||
default: return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Search':
|
||||
$data = array(
|
||||
'q' => $this->getInput('q'),
|
||||
'perpage' => 72,
|
||||
'rating-general' => ($this->getInput('rating-general') === true ? 'on' : 0),
|
||||
'rating-mature' => ($this->getInput('rating-mature') === true ? 'on' : 0),
|
||||
'rating-adult' => ($this->getInput('rating-adult') === true ? 'on' : 0),
|
||||
'range' => $this->getInput('range'),
|
||||
'type-art' => ($this->getInput('type-art') === true ? 'on' : 0),
|
||||
'type-flash' => ($this->getInput('type-flash') === true ? 'on' : 0),
|
||||
'type-photo' => ($this->getInput('type-photo') === true ? 'on' : 0),
|
||||
'type-music' => ($this->getInput('type-music') === true ? 'on' : 0),
|
||||
'type-story' => ($this->getInput('type-story') === true ? 'on' : 0),
|
||||
'type-poetry' => ($this->getInput('type-poetry') === true ? 'on' : 0),
|
||||
'mode' => $this->getInput('mode')
|
||||
);
|
||||
$html = $this->postFASimpleHTMLDOM($data);
|
||||
$limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : 10);
|
||||
$this->itemsFromSubmissionList($html, $limit);
|
||||
break;
|
||||
case 'Browse':
|
||||
$data = array(
|
||||
'cat' => $this->getInput('cat'),
|
||||
'atype' => $this->getInput('atype'),
|
||||
'species' => $this->getInput('species'),
|
||||
'gender' => $this->getInput('gender'),
|
||||
'perpage' => 72,
|
||||
'rating_general' => ($this->getInput('rating_general') === true ? 'on' : 0),
|
||||
'rating_mature' => ($this->getInput('rating_mature') === true ? 'on' : 0),
|
||||
'rating_adult' => ($this->getInput('rating_adult') === true ? 'on' : 0)
|
||||
);
|
||||
$html = $this->postFASimpleHTMLDOM($data);
|
||||
$limit = (is_int($this->getInput('limit-browse')) ? $this->getInput('limit-browse') : 10);
|
||||
$this->itemsFromSubmissionList($html, $limit);
|
||||
break;
|
||||
case 'Journals':
|
||||
$html = $this->getFASimpleHTMLDOM($this->getURI());
|
||||
$limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : -1);
|
||||
$this->itemsFromJournalList($html, $limit);
|
||||
break;
|
||||
case 'Single Journal':
|
||||
$html = $this->getFASimpleHTMLDOM($this->getURI());
|
||||
$this->itemsFromJournal($html);
|
||||
break;
|
||||
case 'Gallery':
|
||||
case 'Scraps':
|
||||
case 'Favorites':
|
||||
case 'Gallery Folder':
|
||||
$html = $this->getFASimpleHTMLDOM($this->getURI());
|
||||
$limit = (is_int($this->getInput('limit')) ? $this->getInput('limit') : 10);
|
||||
$this->itemsFromSubmissionList($html, $limit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function postFASimpleHTMLDOM($data) {
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => http_build_query($data)
|
||||
);
|
||||
$header = array(
|
||||
'Host: ' . parse_url(self::URI, PHP_URL_HOST),
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
'Cookie: ' . self::FA_AUTH_COOKIE
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header, $opts);
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function getFASimpleHTMLDOM($url, $cache = false) {
|
||||
$header = array(
|
||||
'Cookie: ' . self::FA_AUTH_COOKIE
|
||||
);
|
||||
|
||||
if($cache) {
|
||||
$html = getSimpleHTMLDOMCached($url, 86400, $header); // 24 hours
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM($url, $header);
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($html, $url);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
private function itemsFromJournalList($html, $limit) {
|
||||
foreach($html->find('table[id^=jid:]') as $journal) {
|
||||
# allows limit = -1 to mean 'unlimited'
|
||||
if($limit-- === 0) break;
|
||||
|
||||
$item = array();
|
||||
|
||||
$this->setReferrerPolicy($journal);
|
||||
|
||||
$item['uri'] = $journal->find('a', 0)->href;
|
||||
$item['title'] = html_entity_decode($journal->find('a', 0)->plaintext);
|
||||
$item['author'] = $this->getInput('username-journals');
|
||||
$item['timestamp'] = strtotime(
|
||||
$journal->find('span.popup_date', 0)->plaintext);
|
||||
$item['content'] = $journal
|
||||
->find('.alt1 table div.no_overflow', 0)
|
||||
->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function itemsFromJournal($html) {
|
||||
$this->setReferrerPolicy($html);
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI();
|
||||
|
||||
$title = $html->find('.journal-title-box .no_overflow', 0)->plaintext;
|
||||
$title = html_entity_decode($title);
|
||||
$title = trim($title, " \t\n\r\0\x0B" . chr(0xC2) . chr(0xA0));
|
||||
$item['title'] = $title;
|
||||
|
||||
$item['author'] = $html->find('.journal-title-box a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime(
|
||||
$html->find('.journal-title-box span.popup_date', 0)->plaintext);
|
||||
$item['content'] = $html->find('.journal-body', 0)->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function itemsFromSubmissionList($html, $limit) {
|
||||
$cache = ($this->getInput('cache') === true);
|
||||
|
||||
foreach($html->find('section.gallery figure') as $figure) {
|
||||
# allows limit = -1 to mean 'unlimited'
|
||||
if($limit-- === 0) break;
|
||||
|
||||
$item = array();
|
||||
|
||||
$submissionURL = $figure->find('b u a', 0)->href;
|
||||
$imgURL = 'https:' . $figure->find('b u a img', 0)->src;
|
||||
|
||||
$item['uri'] = $submissionURL;
|
||||
$item['title'] = html_entity_decode(
|
||||
$figure->find('figcaption p a[href*=/view/]', 0)->title);
|
||||
$item['author'] = $figure->find('figcaption p a[href*=/user/]', 0)->title;
|
||||
|
||||
if($this->getInput('full') === true) {
|
||||
$submissionHTML = $this->getFASimpleHTMLDOM($submissionURL, $cache);
|
||||
|
||||
$stats = $submissionHTML->find('.stats-container', 0);
|
||||
$item['timestamp'] = strtotime($stats->find('.popup_date', 0)->title);
|
||||
$item['enclosures'] = array(
|
||||
$submissionHTML->find('.actions a[href^=https://d.facdn]', 0)->href
|
||||
);
|
||||
foreach($stats->find('#keywords a') as $keyword) {
|
||||
$item['categories'][] = $keyword->plaintext;
|
||||
}
|
||||
|
||||
$previewSrc = $submissionHTML->find('#submissionImg', 0)
|
||||
->{'data-preview-src'};
|
||||
if($previewSrc) {
|
||||
$imgURL = 'https:' . $previewSrc;
|
||||
}
|
||||
|
||||
$description = $submissionHTML
|
||||
->find('.maintable .maintable tr td.alt1', -1);
|
||||
$this->setReferrerPolicy($description);
|
||||
$description = $description->innertext;
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
<a href="$submissionURL">
|
||||
<img src="{$imgURL}" referrerpolicy="no-referrer" />
|
||||
</a>
|
||||
<p>
|
||||
{$description}
|
||||
</p>
|
||||
EOD;
|
||||
} else {
|
||||
$item['content'] = <<<EOD
|
||||
<a href="$submissionURL">
|
||||
<img src="$imgURL" referrerpolicy="no-referrer" />
|
||||
</a>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function setReferrerPolicy(&$html) {
|
||||
foreach($html->find('img') as $img) {
|
||||
/*
|
||||
* Note: Without the no-referrer policy their CDN sometimes denies requests.
|
||||
* We can't control this for enclosures sadly.
|
||||
* At least tt-rss adds the referrerpolicy on its own.
|
||||
* Alternatively we could not use https for images, but that's not ideal.
|
||||
*/
|
||||
$img->referrerpolicy = 'no-referrer';
|
||||
}
|
||||
}
|
||||
}
|
110
bridges/FurAffinityUserBridge.php
Normal file
110
bridges/FurAffinityUserBridge.php
Normal file
@@ -0,0 +1,110 @@
|
||||
<?php
|
||||
class FurAffinityUserBridge extends BridgeAbstract {
|
||||
const NAME = 'FurAffinity User Gallery';
|
||||
const URI = 'https://www.furaffinity.net';
|
||||
const MAINTAINER = 'CyberJacob';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'searchUsername' => array(
|
||||
'name' => 'Search Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Username to fetch the gallery for'
|
||||
),
|
||||
'loginUsername' => array(
|
||||
'name' => 'Login Username',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
),
|
||||
'loginPassword' => array(
|
||||
'name' => 'Login Password',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$cookies = self::login();
|
||||
$url = self::URI . '/gallery/' . $this->getInput('searchUsername');
|
||||
|
||||
$html = getSimpleHTMLDOM($url, $cookies)
|
||||
or returnServerError('Could not load the user\'s galary page.');
|
||||
|
||||
$submissions = $html->find('section[id=gallery-gallery]', 0)->find('figure');
|
||||
foreach($submissions as $submission) {
|
||||
$item = array();
|
||||
$item['title'] = $submission->find('figcaption', 0)->find('a', 0)->plaintext;
|
||||
|
||||
$thumbnail = $submission->find('a', 0);
|
||||
$thumbnail->href = self::URI . $thumbnail->href;
|
||||
|
||||
$item['content'] = $submission->find('a', 0);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return self::NAME . ' for ' . $this->getInput('searchUsername');
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return self::URI . '/user/' . $this->getInput('searchUsername');
|
||||
}
|
||||
|
||||
private function login() {
|
||||
$ch = curl_init(self::URI . '/login/');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
|
||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
|
||||
$fields = implode('&', array(
|
||||
'action=login',
|
||||
'retard_protection=1',
|
||||
'name=' . urlencode($this->getInput('loginUsername')),
|
||||
'pass=' . urlencode($this->getInput('loginPassword')),
|
||||
'login=Login to Faraffinity'
|
||||
));
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, 5);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
|
||||
|
||||
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
|
||||
|
||||
$data = curl_exec($ch);
|
||||
|
||||
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
$curlError = curl_error($ch);
|
||||
$curlErrno = curl_errno($ch);
|
||||
$curlInfo = curl_getinfo($ch);
|
||||
|
||||
if($data === false)
|
||||
fDebug::log("Cant't download {$url} cUrl error: {$curlError} ({$curlErrno})");
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if($errorCode != 200) {
|
||||
returnServerError(error_get_last());
|
||||
} else {
|
||||
preg_match_all('/^Set-Cookie:\s*([^;]*)/mi', $data, $matches);
|
||||
$cookies = array();
|
||||
|
||||
foreach($matches[1] as $item) {
|
||||
parse_str($item, $cookie);
|
||||
$cookies = array_merge($cookies, $cookie);
|
||||
}
|
||||
|
||||
return $cookies;
|
||||
}
|
||||
}
|
||||
}
|
@@ -96,7 +96,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
}
|
||||
|
||||
private function extractArticleContent($article){
|
||||
$contents = $article->find('section.article-text-classic', 0)->innertext;
|
||||
$contents = $article->find('section.article-text', 1)->innertext;
|
||||
$headline = trim($article->find('p.description', 0)->plaintext);
|
||||
if(!empty($headline))
|
||||
$headline = '<p><b>' . $headline . '</b></p>';
|
||||
@@ -129,6 +129,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
$contents = stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
|
||||
$contents = StripWithDelimiters($contents, '<section class="module-toretain module-propal-nl', '</section>');
|
||||
$contents = stripWithDelimiters($contents, '<script ', '</script>');
|
||||
$contents = stripWithDelimiters($contents, '<script>', '</script>');
|
||||
|
||||
return $headline . trim($contents);
|
||||
}
|
||||
|
@@ -113,8 +113,8 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
break;
|
||||
case 'T':
|
||||
foreach($html->find('li.portal-tutorial') as $tutorialItem) {
|
||||
$url = self::URI . $tutorialItem->find('a', 0)->href;
|
||||
$title = $tutorialItem->find('a', 0)->plaintext;
|
||||
$url = self::URI . $tutorialItem->find('a', 1)->href;
|
||||
$title = $tutorialItem->find('a', 1)->plaintext;
|
||||
$time = $this->findItemDate($tutorialItem);
|
||||
$author = $tutorialItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
|
@@ -8,8 +8,8 @@ class GOGBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$values = getContents('https://www.gog.com/games/ajax/filtered?limit=25&sort=new') or
|
||||
die('Unable to get the news pages from GOG !');
|
||||
$values = getContents('https://www.gog.com/games/ajax/filtered?limit=25&sort=new')
|
||||
or returnServerError('Unable to get the news pages from GOG !');
|
||||
$decodedValues = json_decode($values);
|
||||
|
||||
$limit = 0;
|
||||
@@ -38,8 +38,8 @@ class GOGBridge extends BridgeAbstract {
|
||||
|
||||
private function buildGameContentPage($game) {
|
||||
|
||||
$gameDescriptionText = getContents('https://api.gog.com/products/' . $game->id . '?expand=description') or
|
||||
die('Unable to get game description from GOG !');
|
||||
$gameDescriptionText = getContents('https://api.gog.com/products/' . $game->id . '?expand=description')
|
||||
or returnServerError('Unable to get game description from GOG !');
|
||||
|
||||
$gameDescriptionValue = json_decode($gameDescriptionText);
|
||||
|
||||
|
@@ -40,6 +40,11 @@ class GQMagazineBridge extends BridgeAbstract
|
||||
'data-original' => 'src'
|
||||
);
|
||||
|
||||
const POSSIBLE_TITLES = array(
|
||||
'h2',
|
||||
'h3'
|
||||
);
|
||||
|
||||
private function getDomain() {
|
||||
$domain = $this->getInput('domain');
|
||||
if (empty($domain))
|
||||
@@ -54,6 +59,17 @@ class GQMagazineBridge extends BridgeAbstract
|
||||
return $this->getDomain() . '/' . $this->getInput('page');
|
||||
}
|
||||
|
||||
private function findTitleOf($link) {
|
||||
foreach (self::POSSIBLE_TITLES as $tag) {
|
||||
$title = $link->parent()->find($tag, 0);
|
||||
if($title !== null) {
|
||||
if($title->plaintext !== null) {
|
||||
return $title->plaintext;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI()) or returnServerError('Could not request ' . $this->getURI());
|
||||
@@ -61,31 +77,36 @@ class GQMagazineBridge extends BridgeAbstract
|
||||
// Since GQ don't want simple class scrapping, let's do it the hard way and ... discover content !
|
||||
$main = $html->find('main', 0);
|
||||
foreach ($main->find('a') as $link) {
|
||||
if(strpos($link, $this->getInput('page')))
|
||||
continue;
|
||||
$uri = $link->href;
|
||||
$title = $link->find('h2', 0);
|
||||
$date = $link->find('time', 0);
|
||||
$date = $link->parent()->find('time', 0);
|
||||
|
||||
$item = array();
|
||||
$author = $link->find('span[itemprop=name]', 0);
|
||||
$item['author'] = $author->plaintext;
|
||||
$item['title'] = $title->plaintext;
|
||||
if(substr($uri, 0, 1) === 'h') { // absolute uri
|
||||
$item['uri'] = $uri;
|
||||
} else if(substr($uri, 0, 1) === '/') { // domain relative url
|
||||
$item['uri'] = $this->getDomain() . $uri;
|
||||
} else {
|
||||
$item['uri'] = $this->getDomain() . '/' . $uri;
|
||||
$author = $link->parent()->find('span[itemprop=name]', 0);
|
||||
if($author !== null) {
|
||||
$item['author'] = $author->plaintext;
|
||||
$item['title'] = $this->findTitleOf($link);
|
||||
switch(substr($uri, 0, 1)) {
|
||||
case 'h': // absolute uri
|
||||
$item['uri'] = $uri;
|
||||
break;
|
||||
case '/': // domain relative uri
|
||||
$item['uri'] = $this->getDomain() . $uri;
|
||||
break;
|
||||
default:
|
||||
$item['uri'] = $this->getDomain() . '/' . $uri;
|
||||
}
|
||||
$article = $this->loadFullArticle($item['uri']);
|
||||
if($article) {
|
||||
$item['content'] = $this->replaceUriInHtmlElement($article);
|
||||
} else {
|
||||
$item['content'] = "<strong>Article body couldn't be loaded</strong>. It must be a bug!";
|
||||
}
|
||||
$short_date = $date->datetime;
|
||||
$item['timestamp'] = strtotime($short_date);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
$article = $this->loadFullArticle($item['uri']);
|
||||
if($article) {
|
||||
$item['content'] = $this->replaceUriInHtmlElement($article);
|
||||
} else {
|
||||
$item['content'] = "<strong>Article body couldn't be loaded</strong>. It must be a bug!";
|
||||
}
|
||||
$short_date = $date->datetime;
|
||||
$item['timestamp'] = strtotime($short_date);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,16 +117,7 @@ class GQMagazineBridge extends BridgeAbstract
|
||||
*/
|
||||
private function loadFullArticle($uri){
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
// Once again, that generated css classes madness is an obstacle ... which i can go over easily
|
||||
foreach($html->find('div') as $div) {
|
||||
// List the CSS classes of that div
|
||||
$classes = $div->class;
|
||||
// I can't directly lookup that class since GQ since to generate random names like "ArticleBodySection-fkggUW"
|
||||
if(strpos($classes, 'ArticleBodySection') !== false) {
|
||||
return $div;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return $html->find('section[data-test-id=MainContentWrapper]', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -5,7 +5,7 @@ class GiphyBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'kraoc';
|
||||
const NAME = 'Giphy Bridge';
|
||||
const URI = 'http://giphy.com/';
|
||||
const URI = 'https://giphy.com/';
|
||||
const CACHE_TIMEOUT = 300; //5min
|
||||
const DESCRIPTION = 'Bridge for giphy.com';
|
||||
|
||||
|
27
bridges/GiteaBridge.php
Normal file
27
bridges/GiteaBridge.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
/**
|
||||
* Gitea is a fork of Gogs which may diverge in the future.
|
||||
* https://docs.gitea.io/en-us/
|
||||
*/
|
||||
require_once 'GogsBridge.php';
|
||||
|
||||
class GiteaBridge extends GogsBridge {
|
||||
|
||||
const NAME = 'Gitea';
|
||||
const URI = 'https://gitea.io';
|
||||
const DESCRIPTION = 'Returns the latest issues, commits or releases';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 300; // 5 minutes
|
||||
|
||||
protected function collectReleasesData($html) {
|
||||
$releases = $html->find('#release-list > li')
|
||||
or returnServerError('Unable to find releases');
|
||||
|
||||
foreach($releases as $release) {
|
||||
$this->items[] = array(
|
||||
'uri' => $release->find('a', 0)->href,
|
||||
'title' => 'Release ' . $release->find('h3', 0)->plaintext,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -66,23 +66,37 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
protected function extractIssueEvent($issueNbr, $title, $comment){
|
||||
$comment = $comment->firstChild();
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
|
||||
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
|
||||
private function buildGitHubIssueCommentUri($issue_number, $comment_id) {
|
||||
// https://github.com/<user>/<project>/issues/<issue-number>#<id>
|
||||
return static::URI
|
||||
. $this->getInput('u')
|
||||
. '/'
|
||||
. $this->getInput('p')
|
||||
. '/issues/'
|
||||
. $issue_number
|
||||
. '#'
|
||||
. $comment_id;
|
||||
}
|
||||
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
private function extractIssueEvent($issueNbr, $title, $comment){
|
||||
|
||||
$title .= ' / ' . trim($comment->plaintext);
|
||||
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
|
||||
|
||||
$content = $title;
|
||||
if (null !== $comment->nextSibling()) {
|
||||
$content = $comment->nextSibling()->innertext;
|
||||
if ($comment->nextSibling()->nodeName() === 'span') {
|
||||
$content = $comment->nextSibling()->nextSibling()->innertext;
|
||||
}
|
||||
$author = $comment->find('.author', 0);
|
||||
if ($author) {
|
||||
$author = $author->plaintext;
|
||||
} else {
|
||||
$author = '';
|
||||
}
|
||||
|
||||
$title .= ' / '
|
||||
. trim(str_replace(
|
||||
array('octicon','-'), array(''),
|
||||
$comment->find('.octicon', 0)->getAttribute('class')
|
||||
));
|
||||
|
||||
$content = $comment->plaintext;
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri;
|
||||
@@ -94,22 +108,21 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
protected function extractIssueComment($issueNbr, $title, $comment){
|
||||
$uri = static::URI . $this->getInput('u') . '/'
|
||||
. $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
private function extractIssueComment($issueNbr, $title, $comment){
|
||||
|
||||
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->parent->id);
|
||||
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
$title .= ' / ' . trim(
|
||||
$comment->find('.comment .timeline-comment-header-text', 0)->plaintext
|
||||
$comment->find('.timeline-comment-header-text', 0)->plaintext
|
||||
);
|
||||
|
||||
$content = $comment->find('.comment-body', 0)->innertext;
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri
|
||||
. '#' . $comment->firstChild()->nextSibling()->getAttribute('id');
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = strtotime(
|
||||
$comment->find('relative-time', 0)->getAttribute('datetime')
|
||||
@@ -118,33 +131,29 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
protected function extractIssueComments($issue){
|
||||
private function extractIssueComments($issue){
|
||||
$items = array();
|
||||
$title = $issue->find('.gh-header-title', 0)->plaintext;
|
||||
$issueNbr = trim(
|
||||
substr($issue->find('.gh-header-number', 0)->plaintext, 1)
|
||||
);
|
||||
$comments = $issue->find('.js-discussion', 0);
|
||||
foreach($comments->children() as $comment) {
|
||||
if (!$comment->hasChildNodes()) {
|
||||
continue;
|
||||
}
|
||||
$comment = $comment->firstChild();
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
if (in_array('timeline-comment-wrapper', $classes)) {
|
||||
|
||||
$comments = $issue->find(
|
||||
'.comment, .TimelineItem-badge'
|
||||
);
|
||||
|
||||
foreach($comments as $comment) {
|
||||
if ($comment->hasClass('comment')) {
|
||||
$comment = $comment->parent;
|
||||
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
continue;
|
||||
}
|
||||
while (in_array('discussion-item', $classes)) {
|
||||
} else {
|
||||
$comment = $comment->parent;
|
||||
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
$comment = $comment->nextSibling();
|
||||
if (null == $comment) {
|
||||
break;
|
||||
}
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
}
|
||||
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
@@ -192,8 +201,13 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
);
|
||||
$comments = trim($issue->find('.col-5', 0)->plaintext);
|
||||
$item['content'] .= "\n" . 'Comments: ' . ($comments ? $comments : '0');
|
||||
|
||||
$comment_count = 0;
|
||||
if($span = $issue->find('a[aria-label*="comment"] span', 0)) {
|
||||
$comment_count = $span->plaintext;
|
||||
}
|
||||
|
||||
$item['content'] .= "\n" . 'Comments: ' . $comment_count;
|
||||
$item['uri'] = self::URI
|
||||
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
@@ -216,4 +230,43 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$item['title'] = preg_replace('/\s+/', ' ', $item['title']);
|
||||
});
|
||||
}
|
||||
|
||||
public function detectParameters($url) {
|
||||
|
||||
if(filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED) === false
|
||||
|| strpos($url, self::URI) !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url_components = parse_url($url);
|
||||
$path_segments = array_values(array_filter(explode('/', $url_components['path'])));
|
||||
|
||||
switch(count($path_segments)) {
|
||||
case 2: { // Project issues
|
||||
list($user, $project) = $path_segments;
|
||||
$show_comments = 'off';
|
||||
} break;
|
||||
case 3: { // Project issues with issue comments
|
||||
if($path_segments[2] !== 'issues') {
|
||||
return null;
|
||||
}
|
||||
list($user, $project) = $path_segments;
|
||||
$show_comments = 'on';
|
||||
} break;
|
||||
case 4: { // Issue comments
|
||||
list($user, $project, /* issues */, $issue) = $path_segments;
|
||||
} break;
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'u' => $user,
|
||||
'p' => $project,
|
||||
'c' => isset($show_comments) ? $show_comments : null,
|
||||
'i' => isset($issue) ? $issue : null,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -27,16 +27,16 @@ class GithubSearchBridge extends BridgeAbstract {
|
||||
foreach($html->find('li.repo-list-item') as $element) {
|
||||
$item = array();
|
||||
|
||||
$uri = $element->find('h3 a', 0)->href;
|
||||
$uri = $element->find('.f4 a', 0)->href;
|
||||
$uri = substr(self::URI, 0, -1) . $uri;
|
||||
$item['uri'] = $uri;
|
||||
|
||||
$title = $element->find('h3', 0)->plaintext;
|
||||
$title = $element->find('.f4', 0)->plaintext;
|
||||
$item['title'] = $title;
|
||||
|
||||
// Description
|
||||
if (count($element->find('p.d-inline-block')) != 0) {
|
||||
$content = $element->find('p.d-inline-block', 0)->innertext;
|
||||
if (count($element->find('p.mb-1')) != 0) {
|
||||
$content = $element->find('p.mb-1', 0)->innertext;
|
||||
} else{
|
||||
$content = 'No description';
|
||||
}
|
||||
|
636
bridges/GithubTrendingBridge.php
Normal file
636
bridges/GithubTrendingBridge.php
Normal file
@@ -0,0 +1,636 @@
|
||||
<?php
|
||||
class GithubTrendingBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'liamka';
|
||||
const NAME = 'Github Trending';
|
||||
const URI = 'https://github.com/trending';
|
||||
const URI_ITEM = 'https://github.com';
|
||||
const CACHE_TIMEOUT = 43200; // 12hr
|
||||
const DESCRIPTION = 'See what the GitHub community is most excited repos.';
|
||||
const PARAMETERS = array(
|
||||
'By language' => array(
|
||||
'language' => array(
|
||||
'name' => 'Select language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All languages' => '',
|
||||
'C++' => 'c++',
|
||||
'HTML' => 'html',
|
||||
'Java' => 'java',
|
||||
'JavaScript' => 'javascript',
|
||||
'PHP' => 'php',
|
||||
'Python' => 'python',
|
||||
'Ruby' => 'ruby',
|
||||
'Unknown languages' => 'unknown languages',
|
||||
'1C Enterprise' => '1c enterprise',
|
||||
'4D' => '4d',
|
||||
'ABAP' => 'abap',
|
||||
'ABNF' => 'abnf',
|
||||
'ActionScript' => 'actionscript',
|
||||
'Ada' => 'ada',
|
||||
'Adobe Font Metrics' => 'adobe font metrics',
|
||||
'Agda' => 'agda',
|
||||
'AGS Script' => 'ags script',
|
||||
'Alloy' => 'alloy',
|
||||
'Alpine Abuild' => 'alpine abuild',
|
||||
'Altium Designer' => 'altium designer',
|
||||
'AMPL' => 'ampl',
|
||||
'AngelScript' => 'angelscript',
|
||||
'Ant Build System' => 'ant build system',
|
||||
'ANTLR' => 'antlr',
|
||||
'ApacheConf' => 'apacheconf',
|
||||
'Apex' => 'apex',
|
||||
'API Blueprint' => 'api blueprint',
|
||||
'APL' => 'apl',
|
||||
'Apollo Guidance Computer' => 'apollo guidance computer',
|
||||
'AppleScript' => 'applescript',
|
||||
'Arc' => 'arc',
|
||||
'AsciiDoc' => 'asciidoc',
|
||||
'ASN.1' => 'asn.1',
|
||||
'ASP' => 'asp',
|
||||
'AspectJ' => 'aspectj',
|
||||
'Assembly' => 'assembly',
|
||||
'Asymptote' => 'asymptote',
|
||||
'ATS' => 'ats',
|
||||
'Augeas' => 'augeas',
|
||||
'AutoHotkey' => 'autohotkey',
|
||||
'AutoIt' => 'autoit',
|
||||
'Awk' => 'awk',
|
||||
'Ballerina' => 'ballerina',
|
||||
'Batchfile' => 'batchfile',
|
||||
'Befunge' => 'befunge',
|
||||
'BibTeX' => 'bibtex',
|
||||
'Bison' => 'bison',
|
||||
'BitBake' => 'bitbake',
|
||||
'Blade' => 'blade',
|
||||
'BlitzBasic' => 'blitzbasic',
|
||||
'BlitzMax' => 'blitzmax',
|
||||
'Bluespec' => 'bluespec',
|
||||
'Boo' => 'boo',
|
||||
'Brainfuck' => 'brainfuck',
|
||||
'Brightscript' => 'brightscript',
|
||||
'Zeek' => 'zeek',
|
||||
'C' => 'c',
|
||||
'C#' => 'c#',
|
||||
'C++' => 'c++',
|
||||
'C-ObjDump' => 'c-objdump',
|
||||
'C2hs Haskell' => 'c2hs haskell',
|
||||
'Cabal Config' => 'cabal config',
|
||||
'CartoCSS' => 'cartocss',
|
||||
'Ceylon' => 'ceylon',
|
||||
'Chapel' => 'chapel',
|
||||
'Charity' => 'charity',
|
||||
'ChucK' => 'chuck',
|
||||
'Cirru' => 'cirru',
|
||||
'Clarion' => 'clarion',
|
||||
'Clean' => 'clean',
|
||||
'Click' => 'click',
|
||||
'CLIPS' => 'clips',
|
||||
'Clojure' => 'clojure',
|
||||
'Closure Templates' => 'closure templates',
|
||||
'Cloud Firestore Security Rules' => 'cloud firestore security rules',
|
||||
'CMake' => 'cmake',
|
||||
'COBOL' => 'cobol',
|
||||
'CodeQL' => 'codeql',
|
||||
'CoffeeScript' => 'coffeescript',
|
||||
'ColdFusion' => 'coldfusion',
|
||||
'ColdFusion CFC' => 'coldfusion cfc',
|
||||
'COLLADA' => 'collada',
|
||||
'Common Lisp' => 'common lisp',
|
||||
'Common Workflow Language' => 'common workflow language',
|
||||
'Component Pascal' => 'component pascal',
|
||||
'CoNLL-U' => 'conll-u',
|
||||
'Cool' => 'cool',
|
||||
'Coq' => 'coq',
|
||||
'Cpp-ObjDump' => 'cpp-objdump',
|
||||
'Creole' => 'creole',
|
||||
'Crystal' => 'crystal',
|
||||
'CSON' => 'cson',
|
||||
'Csound' => 'csound',
|
||||
'Csound Document' => 'csound document',
|
||||
'Csound Score' => 'csound score',
|
||||
'CSS' => 'css',
|
||||
'CSV' => 'csv',
|
||||
'Cuda' => 'cuda',
|
||||
'cURL Config' => 'curl config',
|
||||
'CWeb' => 'cweb',
|
||||
'Cycript' => 'cycript',
|
||||
'Cython' => 'cython',
|
||||
'D' => 'd',
|
||||
'D-ObjDump' => 'd-objdump',
|
||||
'Darcs Patch' => 'darcs patch',
|
||||
'Dart' => 'dart',
|
||||
'DataWeave' => 'dataweave',
|
||||
'desktop' => 'desktop',
|
||||
'Dhall' => 'dhall',
|
||||
'Diff' => 'diff',
|
||||
'DIGITAL Command Language' => 'digital command language',
|
||||
'dircolors' => 'dircolors',
|
||||
'DirectX 3D File' => 'directx 3d file',
|
||||
'DM' => 'dm',
|
||||
'DNS Zone' => 'dns zone',
|
||||
'Dockerfile' => 'dockerfile',
|
||||
'Dogescript' => 'dogescript',
|
||||
'DTrace' => 'dtrace',
|
||||
'Dylan' => 'dylan',
|
||||
'E' => 'e',
|
||||
'Eagle' => 'eagle',
|
||||
'Easybuild' => 'easybuild',
|
||||
'EBNF' => 'ebnf',
|
||||
'eC' => 'ec',
|
||||
'Ecere Projects' => 'ecere projects',
|
||||
'ECL' => 'ecl',
|
||||
'ECLiPSe' => 'eclipse',
|
||||
'EditorConfig' => 'editorconfig',
|
||||
'Edje Data Collection' => 'edje data collection',
|
||||
'edn' => 'edn',
|
||||
'Eiffel' => 'eiffel',
|
||||
'EJS' => 'ejs',
|
||||
'Elixir' => 'elixir',
|
||||
'Elm' => 'elm',
|
||||
'Emacs Lisp' => 'emacs lisp',
|
||||
'EmberScript' => 'emberscript',
|
||||
'EML' => 'eml',
|
||||
'EQ' => 'eq',
|
||||
'Erlang' => 'erlang',
|
||||
'F#' => 'f#',
|
||||
'F*' => 'f*',
|
||||
'Factor' => 'factor',
|
||||
'Fancy' => 'fancy',
|
||||
'Fantom' => 'fantom',
|
||||
'Faust' => 'faust',
|
||||
'FIGlet Font' => 'figlet font',
|
||||
'Filebench WML' => 'filebench wml',
|
||||
'Filterscript' => 'filterscript',
|
||||
'fish' => 'fish',
|
||||
'FLUX' => 'flux',
|
||||
'Formatted' => 'formatted',
|
||||
'Forth' => 'forth',
|
||||
'Fortran' => 'fortran',
|
||||
'FreeMarker' => 'freemarker',
|
||||
'Frege' => 'frege',
|
||||
'G-code' => 'g-code',
|
||||
'Game Maker Language' => 'game maker language',
|
||||
'GAML' => 'gaml',
|
||||
'GAMS' => 'gams',
|
||||
'GAP' => 'gap',
|
||||
'GCC Machine Description' => 'gcc machine description',
|
||||
'GDB' => 'gdb',
|
||||
'GDScript' => 'gdscript',
|
||||
'Genie' => 'genie',
|
||||
'Genshi' => 'genshi',
|
||||
'Gentoo Ebuild' => 'gentoo ebuild',
|
||||
'Gentoo Eclass' => 'gentoo eclass',
|
||||
'Gerber Image' => 'gerber image',
|
||||
'Gettext Catalog' => 'gettext catalog',
|
||||
'Gherkin' => 'gherkin',
|
||||
'Git Attributes' => 'git attributes',
|
||||
'Git Config' => 'git config',
|
||||
'GLSL' => 'glsl',
|
||||
'Glyph' => 'glyph',
|
||||
'Glyph Bitmap Distribution Format' => 'glyph bitmap distribution format',
|
||||
'GN' => 'gn',
|
||||
'Gnuplot' => 'gnuplot',
|
||||
'Go' => 'go',
|
||||
'Golo' => 'golo',
|
||||
'Gosu' => 'gosu',
|
||||
'Grace' => 'grace',
|
||||
'Gradle' => 'gradle',
|
||||
'Grammatical Framework' => 'grammatical framework',
|
||||
'Graph Modeling Language' => 'graph modeling language',
|
||||
'GraphQL' => 'graphql',
|
||||
'Graphviz (DOT)' => 'graphviz (dot)',
|
||||
'Groovy' => 'groovy',
|
||||
'Groovy Server Pages' => 'groovy server pages',
|
||||
'Hack' => 'hack',
|
||||
'Haml' => 'haml',
|
||||
'Handlebars' => 'handlebars',
|
||||
'HAProxy' => 'haproxy',
|
||||
'Harbour' => 'harbour',
|
||||
'Haskell' => 'haskell',
|
||||
'Haxe' => 'haxe',
|
||||
'HCL' => 'hcl',
|
||||
'HiveQL' => 'hiveql',
|
||||
'HLSL' => 'hlsl',
|
||||
'HolyC' => 'holyc',
|
||||
'HTML' => 'html',
|
||||
'HTML+Django' => 'html+django',
|
||||
'HTML+ECR' => 'html+ecr',
|
||||
'HTML+EEX' => 'html+eex',
|
||||
'HTML+ERB' => 'html+erb',
|
||||
'HTML+PHP' => 'html+php',
|
||||
'HTML+Razor' => 'html+razor',
|
||||
'HTTP' => 'http',
|
||||
'HXML' => 'hxml',
|
||||
'Hy' => 'hy',
|
||||
'HyPhy' => 'hyphy',
|
||||
'IDL' => 'idl',
|
||||
'Idris' => 'idris',
|
||||
'Ignore List' => 'ignore list',
|
||||
'IGOR Pro' => 'igor pro',
|
||||
'Inform 7' => 'inform 7',
|
||||
'INI' => 'ini',
|
||||
'Inno Setup' => 'inno setup',
|
||||
'Io' => 'io',
|
||||
'Ioke' => 'ioke',
|
||||
'IRC log' => 'irc log',
|
||||
'Isabelle' => 'isabelle',
|
||||
'Isabelle ROOT' => 'isabelle root',
|
||||
'J' => 'j',
|
||||
'Jasmin' => 'jasmin',
|
||||
'Java' => 'java',
|
||||
'Java Properties' => 'java properties',
|
||||
'Java Server Pages' => 'java server pages',
|
||||
'JavaScript' => 'javascript',
|
||||
'JavaScript+ERB' => 'javascript+erb',
|
||||
'JFlex' => 'jflex',
|
||||
'Jison' => 'jison',
|
||||
'Jison Lex' => 'jison lex',
|
||||
'Jolie' => 'jolie',
|
||||
'JSON' => 'json',
|
||||
'JSON with Comments' => 'json with comments',
|
||||
'JSON5' => 'json5',
|
||||
'JSONiq' => 'jsoniq',
|
||||
'JSONLD' => 'jsonld',
|
||||
'Jsonnet' => 'jsonnet',
|
||||
'JSX' => 'jsx',
|
||||
'Julia' => 'julia',
|
||||
'Jupyter Notebook' => 'jupyter notebook',
|
||||
'KiCad Layout' => 'kicad layout',
|
||||
'KiCad Legacy Layout' => 'kicad legacy layout',
|
||||
'KiCad Schematic' => 'kicad schematic',
|
||||
'Kit' => 'kit',
|
||||
'Kotlin' => 'kotlin',
|
||||
'KRL' => 'krl',
|
||||
'LabVIEW' => 'labview',
|
||||
'Lasso' => 'lasso',
|
||||
'Latte' => 'latte',
|
||||
'Lean' => 'lean',
|
||||
'Less' => 'less',
|
||||
'Lex' => 'lex',
|
||||
'LFE' => 'lfe',
|
||||
'LilyPond' => 'lilypond',
|
||||
'Limbo' => 'limbo',
|
||||
'Linker Script' => 'linker script',
|
||||
'Linux Kernel Module' => 'linux kernel module',
|
||||
'Liquid' => 'liquid',
|
||||
'Literate Agda' => 'literate agda',
|
||||
'Literate CoffeeScript' => 'literate coffeescript',
|
||||
'Literate Haskell' => 'literate haskell',
|
||||
'LiveScript' => 'livescript',
|
||||
'LLVM' => 'llvm',
|
||||
'Logos' => 'logos',
|
||||
'Logtalk' => 'logtalk',
|
||||
'LOLCODE' => 'lolcode',
|
||||
'LookML' => 'lookml',
|
||||
'LoomScript' => 'loomscript',
|
||||
'LSL' => 'lsl',
|
||||
'LTspice Symbol' => 'ltspice symbol',
|
||||
'Lua' => 'lua',
|
||||
'M' => 'm',
|
||||
'M4' => 'm4',
|
||||
'M4Sugar' => 'm4sugar',
|
||||
'Makefile' => 'makefile',
|
||||
'Mako' => 'mako',
|
||||
'Markdown' => 'markdown',
|
||||
'Marko' => 'marko',
|
||||
'Mask' => 'mask',
|
||||
'Mathematica' => 'mathematica',
|
||||
'MATLAB' => 'matlab',
|
||||
'Maven POM' => 'maven pom',
|
||||
'Max' => 'max',
|
||||
'MAXScript' => 'maxscript',
|
||||
'mcfunction' => 'mcfunction',
|
||||
'MediaWiki' => 'mediawiki',
|
||||
'Mercury' => 'mercury',
|
||||
'Meson' => 'meson',
|
||||
'Metal' => 'metal',
|
||||
'Microsoft Developer Studio Project' => 'microsoft developer studio project',
|
||||
'MiniD' => 'minid',
|
||||
'Mirah' => 'mirah',
|
||||
'mIRC Script' => 'mirc script',
|
||||
'MLIR' => 'mlir',
|
||||
'Modelica' => 'modelica',
|
||||
'Modula-2' => 'modula-2',
|
||||
'Modula-3' => 'modula-3',
|
||||
'Module Management System' => 'module management system',
|
||||
'Monkey' => 'monkey',
|
||||
'Moocode' => 'moocode',
|
||||
'MoonScript' => 'moonscript',
|
||||
'Motorola 68K Assembly' => 'motorola 68k assembly',
|
||||
'MQL4' => 'mql4',
|
||||
'MQL5' => 'mql5',
|
||||
'MTML' => 'mtml',
|
||||
'MUF' => 'muf',
|
||||
'mupad' => 'mupad',
|
||||
'Muse' => 'muse',
|
||||
'Myghty' => 'myghty',
|
||||
'nanorc' => 'nanorc',
|
||||
'NASL' => 'nasl',
|
||||
'NCL' => 'ncl',
|
||||
'Nearley' => 'nearley',
|
||||
'Nemerle' => 'nemerle',
|
||||
'nesC' => 'nesc',
|
||||
'NetLinx' => 'netlinx',
|
||||
'NetLinx+ERB' => 'netlinx+erb',
|
||||
'NetLogo' => 'netlogo',
|
||||
'NewLisp' => 'newlisp',
|
||||
'Nextflow' => 'nextflow',
|
||||
'Nginx' => 'nginx',
|
||||
'Nim' => 'nim',
|
||||
'Ninja' => 'ninja',
|
||||
'Nit' => 'nit',
|
||||
'Nix' => 'nix',
|
||||
'NL' => 'nl',
|
||||
'NPM Config' => 'npm config',
|
||||
'NSIS' => 'nsis',
|
||||
'Nu' => 'nu',
|
||||
'NumPy' => 'numpy',
|
||||
'ObjDump' => 'objdump',
|
||||
'Object Data Instance Notation' => 'object data instance notation',
|
||||
'Objective-C' => 'objective-c',
|
||||
'Objective-C++' => 'objective-c++',
|
||||
'Objective-J' => 'objective-j',
|
||||
'ObjectScript' => 'objectscript',
|
||||
'OCaml' => 'ocaml',
|
||||
'Odin' => 'odin',
|
||||
'Omgrofl' => 'omgrofl',
|
||||
'ooc' => 'ooc',
|
||||
'Opa' => 'opa',
|
||||
'Opal' => 'opal',
|
||||
'Open Policy Agent' => 'open policy agent',
|
||||
'OpenCL' => 'opencl',
|
||||
'OpenEdge ABL' => 'openedge abl',
|
||||
'OpenQASM' => 'openqasm',
|
||||
'OpenRC runscript' => 'openrc runscript',
|
||||
'OpenSCAD' => 'openscad',
|
||||
'OpenStep Property List' => 'openstep property list',
|
||||
'OpenType Feature File' => 'opentype feature file',
|
||||
'Org' => 'org',
|
||||
'Ox' => 'ox',
|
||||
'Oxygene' => 'oxygene',
|
||||
'Oz' => 'oz',
|
||||
'P4' => 'p4',
|
||||
'Pan' => 'pan',
|
||||
'Papyrus' => 'papyrus',
|
||||
'Parrot' => 'parrot',
|
||||
'Parrot Assembly' => 'parrot assembly',
|
||||
'Parrot Internal Representation' => 'parrot internal representation',
|
||||
'Pascal' => 'pascal',
|
||||
'Pawn' => 'pawn',
|
||||
'Pep8' => 'pep8',
|
||||
'Perl' => 'perl',
|
||||
'PHP' => 'php',
|
||||
'Pic' => 'pic',
|
||||
'Pickle' => 'pickle',
|
||||
'PicoLisp' => 'picolisp',
|
||||
'PigLatin' => 'piglatin',
|
||||
'Pike' => 'pike',
|
||||
'PLpgSQL' => 'plpgsql',
|
||||
'PLSQL' => 'plsql',
|
||||
'Pod' => 'pod',
|
||||
'Pod 6' => 'pod 6',
|
||||
'PogoScript' => 'pogoscript',
|
||||
'Pony' => 'pony',
|
||||
'PostCSS' => 'postcss',
|
||||
'PostScript' => 'postscript',
|
||||
'POV-Ray SDL' => 'pov-ray sdl',
|
||||
'PowerBuilder' => 'powerbuilder',
|
||||
'PowerShell' => 'powershell',
|
||||
'Prisma' => 'prisma',
|
||||
'Processing' => 'processing',
|
||||
'Proguard' => 'proguard',
|
||||
'Prolog' => 'prolog',
|
||||
'Propeller Spin' => 'propeller spin',
|
||||
'Protocol Buffer' => 'protocol buffer',
|
||||
'Public Key' => 'public key',
|
||||
'Pug' => 'pug',
|
||||
'Puppet' => 'puppet',
|
||||
'Pure Data' => 'pure data',
|
||||
'PureBasic' => 'purebasic',
|
||||
'PureScript' => 'purescript',
|
||||
'Python' => 'python',
|
||||
'Python console' => 'python console',
|
||||
'Python traceback' => 'python traceback',
|
||||
'q' => 'q',
|
||||
'QMake' => 'qmake',
|
||||
'QML' => 'qml',
|
||||
'Quake' => 'quake',
|
||||
'R' => 'r',
|
||||
'Racket' => 'racket',
|
||||
'Ragel' => 'ragel',
|
||||
'Raku' => 'raku',
|
||||
'RAML' => 'raml',
|
||||
'Rascal' => 'rascal',
|
||||
'Raw token data' => 'raw token data',
|
||||
'RDoc' => 'rdoc',
|
||||
'Readline Config' => 'readline config',
|
||||
'REALbasic' => 'realbasic',
|
||||
'Reason' => 'reason',
|
||||
'Rebol' => 'rebol',
|
||||
'Red' => 'red',
|
||||
'Redcode' => 'redcode',
|
||||
'Regular Expression' => 'regular expression',
|
||||
// 'Ren'Py' => 'ren'py',
|
||||
'RenderScript' => 'renderscript',
|
||||
'reStructuredText' => 'restructuredtext',
|
||||
'REXX' => 'rexx',
|
||||
'RHTML' => 'rhtml',
|
||||
'Rich Text Format' => 'rich text format',
|
||||
'Ring' => 'ring',
|
||||
'Riot' => 'riot',
|
||||
'RMarkdown' => 'rmarkdown',
|
||||
'RobotFramework' => 'robotframework',
|
||||
'Roff' => 'roff',
|
||||
'Roff Manpage' => 'roff manpage',
|
||||
'Rouge' => 'rouge',
|
||||
'RPC' => 'rpc',
|
||||
'RPM Spec' => 'rpm spec',
|
||||
'Ruby' => 'ruby',
|
||||
'RUNOFF' => 'runoff',
|
||||
'Rust' => 'rust',
|
||||
'Sage' => 'sage',
|
||||
'SaltStack' => 'saltstack',
|
||||
'SAS' => 'sas',
|
||||
'Sass' => 'sass',
|
||||
'Scala' => 'scala',
|
||||
'Scaml' => 'scaml',
|
||||
'Scheme' => 'scheme',
|
||||
'Scilab' => 'scilab',
|
||||
'SCSS' => 'scss',
|
||||
'sed' => 'sed',
|
||||
'Self' => 'self',
|
||||
'ShaderLab' => 'shaderlab',
|
||||
'Shell' => 'shell',
|
||||
'ShellSession' => 'shellsession',
|
||||
'Shen' => 'shen',
|
||||
'Slash' => 'slash',
|
||||
'Slice' => 'slice',
|
||||
'Slim' => 'slim',
|
||||
'Smali' => 'smali',
|
||||
'Smalltalk' => 'smalltalk',
|
||||
'Smarty' => 'smarty',
|
||||
'SmPL' => 'smpl',
|
||||
'SMT' => 'smt',
|
||||
'Solidity' => 'solidity',
|
||||
'SourcePawn' => 'sourcepawn',
|
||||
'SPARQL' => 'sparql',
|
||||
'Spline Font Database' => 'spline font database',
|
||||
'SQF' => 'sqf',
|
||||
'SQL' => 'sql',
|
||||
'SQLPL' => 'sqlpl',
|
||||
'Squirrel' => 'squirrel',
|
||||
'SRecode Template' => 'srecode template',
|
||||
'SSH Config' => 'ssh config',
|
||||
'Stan' => 'stan',
|
||||
'Standard ML' => 'standard ml',
|
||||
'Starlark' => 'starlark',
|
||||
'Stata' => 'stata',
|
||||
'STON' => 'ston',
|
||||
'Stylus' => 'stylus',
|
||||
'SubRip Text' => 'subrip text',
|
||||
'SugarSS' => 'sugarss',
|
||||
'SuperCollider' => 'supercollider',
|
||||
'Svelte' => 'svelte',
|
||||
'SVG' => 'svg',
|
||||
'Swift' => 'swift',
|
||||
'SWIG' => 'swig',
|
||||
'SystemVerilog' => 'systemverilog',
|
||||
'Tcl' => 'tcl',
|
||||
'Tcsh' => 'tcsh',
|
||||
'Tea' => 'tea',
|
||||
'Terra' => 'terra',
|
||||
'TeX' => 'tex',
|
||||
'Texinfo' => 'texinfo',
|
||||
'Text' => 'text',
|
||||
'Textile' => 'textile',
|
||||
'Thrift' => 'thrift',
|
||||
'TI Program' => 'ti program',
|
||||
'TLA' => 'tla',
|
||||
'TOML' => 'toml',
|
||||
'TSQL' => 'tsql',
|
||||
'TSX' => 'tsx',
|
||||
'Turing' => 'turing',
|
||||
'Turtle' => 'turtle',
|
||||
'Twig' => 'twig',
|
||||
'TXL' => 'txl',
|
||||
'Type Language' => 'type language',
|
||||
'TypeScript' => 'typescript',
|
||||
'Unified Parallel C' => 'unified parallel c',
|
||||
'Unity3D Asset' => 'unity3d asset',
|
||||
'Unix Assembly' => 'unix assembly',
|
||||
'Uno' => 'uno',
|
||||
'UnrealScript' => 'unrealscript',
|
||||
'UrWeb' => 'urweb',
|
||||
'V' => 'v',
|
||||
'Vala' => 'vala',
|
||||
'VBA' => 'vba',
|
||||
'VBScript' => 'vbscript',
|
||||
'VCL' => 'vcl',
|
||||
'Verilog' => 'verilog',
|
||||
'VHDL' => 'vhdl',
|
||||
'Vim script' => 'vim script',
|
||||
'Vim Snippet' => 'vim snippet',
|
||||
'Visual Basic .NET' => 'visual basic .net',
|
||||
'Visual Basic .NET' => 'visual basic .net',
|
||||
'Volt' => 'volt',
|
||||
'Vue' => 'vue',
|
||||
'Wavefront Material' => 'wavefront material',
|
||||
'Wavefront Object' => 'wavefront object',
|
||||
'wdl' => 'wdl',
|
||||
'Web Ontology Language' => 'web ontology language',
|
||||
'WebAssembly' => 'webassembly',
|
||||
'WebIDL' => 'webidl',
|
||||
'WebVTT' => 'webvtt',
|
||||
'Wget Config' => 'wget config',
|
||||
'Windows Registry Entries' => 'windows registry entries',
|
||||
'wisp' => 'wisp',
|
||||
'Wollok' => 'wollok',
|
||||
'World of Warcraft Addon Data' => 'world of warcraft addon data',
|
||||
'X BitMap' => 'x bitmap',
|
||||
'X Font Directory Index' => 'x font directory index',
|
||||
'X PixMap' => 'x pixmap',
|
||||
'X10' => 'x10',
|
||||
'xBase' => 'xbase',
|
||||
'XC' => 'xc',
|
||||
'XCompose' => 'xcompose',
|
||||
'XML' => 'xml',
|
||||
'XML Property List' => 'xml property list',
|
||||
'Xojo' => 'xojo',
|
||||
'XPages' => 'xpages',
|
||||
'XProc' => 'xproc',
|
||||
'XQuery' => 'xquery',
|
||||
'XS' => 'xs',
|
||||
'XSLT' => 'xslt',
|
||||
'Xtend' => 'xtend',
|
||||
'Yacc' => 'yacc',
|
||||
'YAML' => 'yaml',
|
||||
'YANG' => 'yang',
|
||||
'YARA' => 'yara',
|
||||
'YASnippet' => 'yasnippet',
|
||||
'ZAP' => 'zap',
|
||||
'Zeek' => 'zeek',
|
||||
'ZenScript' => 'zenscript',
|
||||
'Zephir' => 'zephir',
|
||||
'Zig' => 'zig',
|
||||
'ZIL' => 'zil',
|
||||
'Zimpl' => 'zimpl',
|
||||
),
|
||||
'defaultValue' => 'All languages'
|
||||
)
|
||||
),
|
||||
|
||||
'global' => array(
|
||||
'date_range' => array(
|
||||
'name' => 'Date range',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Today' => 'today',
|
||||
'Weekly' => 'weekly',
|
||||
'Monthly' => 'monthly',
|
||||
),
|
||||
'defaultValue' => 'today'
|
||||
)
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$params = array('since' => urlencode($this->getInput('date_range')));
|
||||
$url = self::URI . '/' . $this->getInput('language') . '?' . http_build_query($params);
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$this->items = array();
|
||||
foreach($html->find('.Box-row') as $element) {
|
||||
$item = array();
|
||||
|
||||
// URI
|
||||
$item['uri'] = self::URI_ITEM . $element->find('h1 a', 0)->href;
|
||||
|
||||
// Title
|
||||
$item['title'] = str_replace(' ', '', trim(strip_tags($element->find('h1 a', 0)->plaintext)));
|
||||
|
||||
// Description
|
||||
$item['content'] = trim(strip_tags($element->find('p.text-gray', 0)->innertext));
|
||||
|
||||
// Time
|
||||
$item['timestamp'] = time();
|
||||
|
||||
// TODO: Proxy?
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if($this->getInput('language') == '') {
|
||||
return self::NAME . ': all';
|
||||
} elseif (!is_null($this->getInput('language'))) {
|
||||
return self::NAME . ': ' . $this->getInput('language');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -3,34 +3,78 @@ class GizmodoBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'polopollo';
|
||||
const NAME = 'Gizmodo';
|
||||
const URI = 'http://gizmodo.com/';
|
||||
const URI = 'https://gizmodo.com';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns the newest posts from Gizmodo (full text).';
|
||||
const DESCRIPTION = 'Returns the newest posts from Gizmodo.';
|
||||
|
||||
protected function parseItem($item){
|
||||
protected function parseItem($item) {
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$articleHTMLContent = getSimpleHTMLDOMCached($item['uri']);
|
||||
if(!$articleHTMLContent) {
|
||||
$text = 'Could not load ' . $item['uri'];
|
||||
} else {
|
||||
$text = $articleHTMLContent->find('div.entry-content', 0)->innertext;
|
||||
foreach($articleHTMLContent->find('pagespeed_iframe') as $element) {
|
||||
$text .= '<p>link to a iframe (could be a video): <a href="'
|
||||
. $element->src
|
||||
. '">'
|
||||
. $element->src
|
||||
. '</a></p><br>';
|
||||
}
|
||||
$html = getSimpleHTMLDOMCached($item['uri'])
|
||||
or returnServerError('Could not request: ' . $item['uri']);
|
||||
|
||||
$text = strip_tags($text, '<p><b><a><blockquote><img><em>');
|
||||
}
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
$this->stripTags($html);
|
||||
$this->handleFigureTags($html);
|
||||
$this->handleIframeTags($html);
|
||||
|
||||
// Get header image
|
||||
$image = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$item['content'] = $html->find('div.js_post-content', 0)->innertext;
|
||||
|
||||
// Get categories
|
||||
$categories = explode(',', $html->find('meta[name="keywords"]', 0)->content);
|
||||
$item['categories'] = array_map('trim', $categories);
|
||||
|
||||
$item['enclosures'][] = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$item['content'] = $text;
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://feeds.gawker.com/gizmodo/full');
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas(self::URI . '/rss', 20);
|
||||
}
|
||||
|
||||
private function stripTags($html) {
|
||||
foreach ($html->find('aside') as $aside) {
|
||||
$aside->outertext = '';
|
||||
}
|
||||
|
||||
foreach ($html->find('div.ad-unit') as $div) {
|
||||
$div->outertext = '';
|
||||
}
|
||||
|
||||
foreach ($html->find('script') as $script) {
|
||||
$script->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
private function handleFigureTags($html) {
|
||||
foreach ($html->find('figure') as $index => $figure) {
|
||||
|
||||
if (isset($figure->attr['data-id'])) {
|
||||
$id = $figure->attr['data-id'];
|
||||
$format = $figure->attr['data-format'];
|
||||
|
||||
} else {
|
||||
$img = $figure->find('img', 0);
|
||||
$id = $img->attr['data-chomp-id'];
|
||||
$format = $img->attr['data-format'];
|
||||
$figure->find('div.img-permalink-sub-wrapper', 0)->style = '';
|
||||
}
|
||||
|
||||
$imageUrl = 'https://i.kinja-img.com/gawker-media/image/upload/' . $id . '.' . $format;
|
||||
|
||||
$figure->find('span', 0)->outertext = <<<EOD
|
||||
<img src="{$imageUrl}">
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleIframeTags($html) {
|
||||
foreach($html->find('iframe') as $iframe) {
|
||||
$iframe->src = urljoin($this->getURI(), $iframe->src);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -141,7 +141,7 @@ class GlassdoorBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function collectReviewData($html, $limit) {
|
||||
$reviews = $html->find('#EmployerReviews li[id^="empReview]')
|
||||
$reviews = $html->find('#ReviewsFeed li[id^="empReview]')
|
||||
or returnServerError('Unable to find reviews!');
|
||||
|
||||
foreach($reviews as $review) {
|
||||
@@ -153,7 +153,19 @@ class GlassdoorBridge extends BridgeAbstract {
|
||||
$item['timestamp'] = strtotime($review->find('time', 0)->datetime);
|
||||
|
||||
$mainText = $review->find('p.mainText', 0)->plaintext;
|
||||
$description = $review->find('div.prosConsAdvice', 0)->innertext;
|
||||
|
||||
$description = '';
|
||||
foreach($review->find('div.description p') as $p) {
|
||||
|
||||
if ($p->hasClass('strong')) {
|
||||
$p->tag = 'strong';
|
||||
$p->removeClass('strong');
|
||||
}
|
||||
|
||||
$description .= $p;
|
||||
|
||||
}
|
||||
|
||||
$item['content'] = "<p>{$mainText}</p><p>{$description}</p>";
|
||||
|
||||
$this->items[] = $item;
|
||||
|
@@ -28,7 +28,7 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request GoComics: ' . $link);
|
||||
$imagelink = $page->find('.img-fluid', 1)->src;
|
||||
$imagelink = $page->find('.comic.container', 0)->getAttribute('data-image');
|
||||
$date = explode('/', $link);
|
||||
|
||||
$item['id'] = $imagelink;
|
||||
|
206
bridges/GogsBridge.php
Normal file
206
bridges/GogsBridge.php
Normal file
@@ -0,0 +1,206 @@
|
||||
<?php
|
||||
class GogsBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Gogs';
|
||||
const URI = 'https://gogs.io';
|
||||
const DESCRIPTION = 'Returns the latest issues, commits or releases';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 300; // 5 minutes
|
||||
|
||||
const PARAMETERS = array(
|
||||
'global' => array(
|
||||
'host' => array(
|
||||
'name' => 'Host',
|
||||
'exampleValue' => 'https://gogs.io',
|
||||
'required' => true,
|
||||
'title' => 'Host name without trailing slash',
|
||||
),
|
||||
'user' => array(
|
||||
'name' => 'Username',
|
||||
'exampleValue' => 'gogs',
|
||||
'required' => true,
|
||||
'title' => 'User name as it appears in the URL',
|
||||
),
|
||||
'project' => array(
|
||||
'name' => 'Project name',
|
||||
'exampleValue' => 'gogs',
|
||||
'required' => true,
|
||||
'title' => 'Project name as it appears in the URL',
|
||||
),
|
||||
),
|
||||
'Commits' => array(
|
||||
'branch' => array(
|
||||
'name' => 'Branch name',
|
||||
'defaultValue' => 'master',
|
||||
'required' => true,
|
||||
'title' => 'Branch name as it appears in the URL',
|
||||
),
|
||||
),
|
||||
'Issues' => array(
|
||||
'include_description' => array(
|
||||
'name' => 'Include issue description',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to include the issue description',
|
||||
),
|
||||
),
|
||||
'Single issue' => array(
|
||||
'issue' => array(
|
||||
'name' => 'Issue number',
|
||||
'type' => 'number',
|
||||
'exampleValue' => 102,
|
||||
'required' => true,
|
||||
'title' => 'Issue number from the issues list',
|
||||
),
|
||||
),
|
||||
'Releases' => array(),
|
||||
);
|
||||
|
||||
private $title = '';
|
||||
|
||||
/**
|
||||
* Note: detectParamters doesn't make sense for this bridge because there is
|
||||
* no "single" host for this service. Anyone can host it.
|
||||
*/
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits': {
|
||||
return $this->getInput('host')
|
||||
. '/' . $this->getInput('user')
|
||||
. '/' . $this->getInput('project')
|
||||
. '/commits/' . $this->getInput('branch');
|
||||
} break;
|
||||
case 'Issues': {
|
||||
return $this->getInput('host')
|
||||
. '/' . $this->getInput('user')
|
||||
. '/' . $this->getInput('project')
|
||||
. '/issues/';
|
||||
} break;
|
||||
case 'Single issue': {
|
||||
return $this->getInput('host')
|
||||
. '/' . $this->getInput('user')
|
||||
. '/' . $this->getInput('project')
|
||||
. '/issues/' . $this->getInput('issue');
|
||||
} break;
|
||||
case 'Releases': {
|
||||
return $this->getInput('host')
|
||||
. '/' . $this->getInput('user')
|
||||
. '/' . $this->getInput('project')
|
||||
. '/releases/';
|
||||
} break;
|
||||
default: return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
case 'Issues':
|
||||
case 'Releases': return $this->title . ' ' . $this->queriedContext;
|
||||
case 'Single issue': return $this->title . ' Issue ' . $this->getInput('issue');
|
||||
default: return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://gogs.io/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$this->title = $html->find('[property="og:title"]', 0)->content;
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits': {
|
||||
$this->collectCommitsData($html);
|
||||
} break;
|
||||
case 'Issues': {
|
||||
$this->collectIssuesData($html);
|
||||
} break;
|
||||
case 'Single issue': {
|
||||
$this->collectSingleIssueData($html);
|
||||
} break;
|
||||
case 'Releases': {
|
||||
$this->collectReleasesData($html);
|
||||
} break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
protected function collectCommitsData($html) {
|
||||
$commits = $html->find('#commits-table tbody tr')
|
||||
or returnServerError('Unable to find commits');
|
||||
|
||||
foreach($commits as $commit) {
|
||||
$this->items[] = array(
|
||||
'uri' => $commit->find('a.sha', 0)->href,
|
||||
'title' => $commit->find('.message span', 0)->plaintext,
|
||||
'author' => $commit->find('.author', 0)->plaintext,
|
||||
'timestamp' => $commit->find('.time-since', 0)->title,
|
||||
'uid' => $commit->find('.sha', 0)->plaintext,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function collectIssuesData($html) {
|
||||
$issues = $html->find('.issue.list li')
|
||||
or returnServerError('Unable to find issues');
|
||||
|
||||
foreach($issues as $issue) {
|
||||
$uri = $issue->find('a', 0)->href;
|
||||
|
||||
$item = array(
|
||||
'uri' => $uri,
|
||||
'title' => $issue->find('.label', 0)->plaintext . ' | ' . $issue->find('a.title', 0)->plaintext,
|
||||
'author' => $issue->find('.desc a', 0)->plaintext,
|
||||
'timestamp' => $issue->find('.time-since', 0)->title,
|
||||
'uid' => $issue->find('.label', 0)->plaintext,
|
||||
);
|
||||
|
||||
if($this->getInput('include_description')) {
|
||||
$issue_html = getSimpleHTMLDOMCached($uri, 3600)
|
||||
or returnServerError('Unable to load issue description');
|
||||
|
||||
$issue_html = defaultLinkTo($issue_html, $uri);
|
||||
|
||||
$item['content'] = $issue_html->find('.comment .markdown', 0);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
protected function collectSingleIssueData($html) {
|
||||
$comments = $html->find('.comments .comment')
|
||||
or returnServerError('Unable to find comments');
|
||||
|
||||
foreach($comments as $comment) {
|
||||
$this->items[] = array(
|
||||
'uri' => $comment->find('a[href*="#issue"]', 0)->href,
|
||||
'title' => $comment->find('span', 0)->plaintext,
|
||||
'author' => $comment->find('.content a', 0)->plaintext,
|
||||
'timestamp' => $comment->find('.time-since', 0)->title,
|
||||
'content' => $comment->find('.markdown', 0),
|
||||
);
|
||||
}
|
||||
|
||||
$this->items = array_reverse($this->items);
|
||||
}
|
||||
|
||||
protected function collectReleasesData($html) {
|
||||
$releases = $html->find('#release-list li')
|
||||
or returnServerError('Unable to find releases');
|
||||
|
||||
foreach($releases as $release) {
|
||||
$this->items[] = array(
|
||||
'uri' => $release->find('a', 0)->href,
|
||||
'title' => 'Release ' . $release->find('h4', 0)->plaintext,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -25,35 +25,37 @@ class GoogleSearchBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI
|
||||
. 'search?q='
|
||||
. urlencode($this->getInput('q'))
|
||||
. '&num=100&complete=0&tbs=qdr:y,sbd:1')
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
$emIsRes = $html->find('div[id=ires]', 0);
|
||||
$emIsRes = $html->find('div[id=res]', 0);
|
||||
|
||||
if(!is_null($emIsRes)) {
|
||||
foreach($emIsRes->find('div[class=g]') as $element) {
|
||||
|
||||
$item = array();
|
||||
|
||||
// Extract direct URL from google href (eg. /url?q=...)
|
||||
$t = $element->find('a[href]', 0)->href;
|
||||
$item['uri'] = '' . $t;
|
||||
parse_str(parse_url($t, PHP_URL_QUERY), $parameters);
|
||||
if(isset($parameters['q'])) {
|
||||
$item['uri'] = $parameters['q'];
|
||||
}
|
||||
|
||||
$item['uri'] = htmlspecialchars_decode($t);
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['content'] = $element->find('span[class=st]', 0)->plaintext;
|
||||
$item['content'] = $element->find('span[class=aCOpRe]', 0)->plaintext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('q'))) {
|
||||
return self::URI
|
||||
. 'search?q='
|
||||
. urlencode($this->getInput('q'))
|
||||
. '&num=100&complete=0&tbs=qdr:y,sbd:1';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return $this->getInput('q') . ' - Google search';
|
||||
|
@@ -2,7 +2,7 @@
|
||||
class HDWallpapersBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'nel50n';
|
||||
const NAME = 'HD Wallpapers Bridge';
|
||||
const URI = 'http://www.hdwallpapers.in/';
|
||||
const URI = 'https://www.hdwallpapers.in/';
|
||||
const CACHE_TIMEOUT = 43200; //12h
|
||||
const DESCRIPTION = 'Returns the latests wallpapers from HDWallpapers';
|
||||
|
||||
@@ -72,7 +72,7 @@ class HDWallpapersBridge extends BridgeAbstract {
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('c')) && !is_null($this->getInput('r'))) {
|
||||
return 'HDWallpapers - '
|
||||
. str_replace(['__', '_'], [' & ', ' '], $this->getInput('c'))
|
||||
. str_replace(array('__', '_'), array(' & ', ' '), $this->getInput('c'))
|
||||
. ' ['
|
||||
. $this->getInput('r')
|
||||
. ']';
|
||||
|
138
bridges/HaveIBeenPwnedBridge.php
Normal file
138
bridges/HaveIBeenPwnedBridge.php
Normal file
@@ -0,0 +1,138 @@
|
||||
<?php
|
||||
class HaveIBeenPwnedBridge extends BridgeAbstract {
|
||||
const NAME = 'Have I Been Pwned (HIBP) Bridge';
|
||||
const URI = 'https://haveibeenpwned.com';
|
||||
const DESCRIPTION = 'Returns list of Pwned websites';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'order' => array(
|
||||
'name' => 'Order by',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Breach date' => 'breachDate',
|
||||
'Date added to HIBP' => 'dateAdded',
|
||||
),
|
||||
'defaultValue' => 'dateAdded',
|
||||
),
|
||||
'item_limit' => array(
|
||||
'name' => 'Limit number of returned items',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 20,
|
||||
)
|
||||
));
|
||||
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
private $breachDateRegex = '/Breach date: ([0-9]{1,2} [A-Z-a-z]+ [0-9]{4})/';
|
||||
private $dateAddedRegex = '/Date added to HIBP: ([0-9]{1,2} [A-Z-a-z]+ [0-9]{4})/';
|
||||
private $accountsRegex = '/Compromised accounts: ([0-9,]+)/';
|
||||
|
||||
private $breaches = array();
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . '/PwnedWebsites')
|
||||
or returnServerError('Could not request: ' . self::URI . '/PwnedWebsites');
|
||||
|
||||
$breaches = array();
|
||||
|
||||
foreach($html->find('div.row') as $breach) {
|
||||
$item = array();
|
||||
|
||||
if ($breach->class != 'row') {
|
||||
continue;
|
||||
}
|
||||
|
||||
preg_match($this->breachDateRegex, $breach->find('p', 1)->plaintext, $breachDate)
|
||||
or returnServerError('Could not extract details');
|
||||
|
||||
preg_match($this->dateAddedRegex, $breach->find('p', 1)->plaintext, $dateAdded)
|
||||
or returnServerError('Could not extract details');
|
||||
|
||||
preg_match($this->accountsRegex, $breach->find('p', 1)->plaintext, $accounts)
|
||||
or returnServerError('Could not extract details');
|
||||
|
||||
$permalink = $breach->find('p', 1)->find('a', 0)->href;
|
||||
|
||||
// Remove permalink
|
||||
$breach->find('p', 1)->find('a', 0)->outertext = '';
|
||||
|
||||
$item['title'] = html_entity_decode($breach->find('h3', 0)->plaintext, ENT_QUOTES)
|
||||
. ' - ' . $accounts[1] . ' breached accounts';
|
||||
$item['dateAdded'] = strtotime($dateAdded[1]);
|
||||
$item['breachDate'] = strtotime($breachDate[1]);
|
||||
$item['uri'] = self::URI . '/PwnedWebsites' . $permalink;
|
||||
|
||||
$item['content'] = '<p>' . $breach->find('p', 0)->innertext . '</p>';
|
||||
$item['content'] .= '<p>' . $this->breachType($breach) . '</p>';
|
||||
$item['content'] .= '<p>' . $breach->find('p', 1)->innertext . '</p>';
|
||||
|
||||
$this->breaches[] = $item;
|
||||
}
|
||||
|
||||
$this->orderBreaches();
|
||||
$this->createItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract data breach type(s)
|
||||
*/
|
||||
private function breachType($breach) {
|
||||
|
||||
$content = '';
|
||||
|
||||
if ($breach->find('h3 > i', 0)) {
|
||||
|
||||
foreach ($breach->find('h3 > i') as $i) {
|
||||
$content .= $i->title . '.<br>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $content;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Order Breaches by date added or date breached
|
||||
*/
|
||||
private function orderBreaches() {
|
||||
|
||||
$sortBy = $this->getInput('order');
|
||||
$sort = array();
|
||||
|
||||
foreach ($this->breaches as $key => $item) {
|
||||
$sort[$key] = $item[$sortBy];
|
||||
}
|
||||
|
||||
array_multisort($sort, SORT_DESC, $this->breaches);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Create items from breaches array
|
||||
*/
|
||||
private function createItems() {
|
||||
|
||||
$limit = $this->getInput('item_limit');
|
||||
|
||||
if ($limit < 1) {
|
||||
$limit = 20;
|
||||
}
|
||||
|
||||
foreach ($this->breaches as $breach) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $breach['title'];
|
||||
$item['timestamp'] = $breach[$this->getInput('order')];
|
||||
$item['uri'] = $breach['uri'];
|
||||
$item['content'] = $breach['content'];
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -40,18 +40,15 @@ class HeiseBridge extends FeedExpander {
|
||||
|
||||
protected function parseItem($feedItem) {
|
||||
$item = parent::parseItem($feedItem);
|
||||
$uri = $item['uri'];
|
||||
$uri = $item['uri'] . '&seite=all';
|
||||
|
||||
do {
|
||||
$article = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not open article: ' . $uri);
|
||||
$article = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not open article: ' . $uri);
|
||||
|
||||
if ($article) {
|
||||
$article = defaultLinkTo($article, $uri);
|
||||
$item = $this->addArticleToItem($item, $article);
|
||||
|
||||
if($next = $article->find('.pagination a[rel="next"]', 0))
|
||||
$uri = $next->href;
|
||||
} while ($next);
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
@@ -62,6 +59,9 @@ class HeiseBridge extends FeedExpander {
|
||||
|
||||
$content = $article->find('div[class*="article-content"]', 0);
|
||||
|
||||
if ($content == null)
|
||||
$content = $article->find('#article_content', 0);
|
||||
|
||||
foreach($content->find('p, h3, ul, table, pre, img') as $element) {
|
||||
$item['content'] .= $element;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class HentaiHavenBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'albirew';
|
||||
const NAME = 'Hentai Haven';
|
||||
const URI = 'http://hentaihaven.org/';
|
||||
const URI = 'https://hentaihaven.org/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns releases from Hentai Haven';
|
||||
|
||||
|
65
bridges/IGNBridge.php
Normal file
65
bridges/IGNBridge.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
class IGNBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'IceWreck';
|
||||
const NAME = 'IGN Bridge';
|
||||
const URI = 'https://www.ign.com/';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'RSS Feed For IGN';
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://feeds.ign.com/ign/all', 15);
|
||||
}
|
||||
|
||||
// IGNs feed is both hidden and incomplete. This bridge tries to fix this.
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
|
||||
// $articlePage gets the entire page's contents
|
||||
$articlePage = getSimpleHTMLDOM($newsItem->link);
|
||||
|
||||
// List of BS elements
|
||||
$uselessElements = array(
|
||||
'.wiki-page-tools',
|
||||
'.feedback-container',
|
||||
'.paging-container',
|
||||
'.dropdown-wrapper',
|
||||
'.mw-editsection',
|
||||
'.jsx-4115608983',
|
||||
'.jsx-4213937408',
|
||||
'.commerce-container',
|
||||
'.widget-container',
|
||||
'.newsletter-signup-button'
|
||||
);
|
||||
|
||||
// Remove useless elements
|
||||
foreach($uselessElements as $uslElement) {
|
||||
foreach($articlePage->find($uslElement) as $jsWidget) {
|
||||
$jsWidget->remove();
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* NOTE: Though articles and wiki/howtos have seperate styles of pages, there is no mechanism
|
||||
* for handling them seperately as it just ignores the DOM querys which it does not find.
|
||||
* (and their scraping)
|
||||
*/
|
||||
|
||||
// For Articles
|
||||
$article = $articlePage->find('section.article-page', 0);
|
||||
// add in verdicts in articles, reviews etc
|
||||
foreach($articlePage->find('div.article-section') as $element) {
|
||||
$article = $article . $element;
|
||||
}
|
||||
|
||||
// For Wikis and HowTos
|
||||
foreach($articlePage->find('.wiki-page') as $wikiContents) {
|
||||
$article = $article . $wikiContents;
|
||||
}
|
||||
|
||||
// Add content to feed
|
||||
$item['content'] = $article;
|
||||
return $item;
|
||||
}
|
||||
}
|
245
bridges/IndeedBridge.php
Normal file
245
bridges/IndeedBridge.php
Normal file
@@ -0,0 +1,245 @@
|
||||
<?php
|
||||
class IndeedBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Indeed';
|
||||
const URI = 'https://www.indeed.com/';
|
||||
const DESCRIPTION = 'Returns reviews and comments for a company of your choice';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 14400; // 4 hours
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'c' => array(
|
||||
'name' => 'Company',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Company name',
|
||||
'exampleValue' => 'GitHub',
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'language' => array(
|
||||
'name' => 'Language Code',
|
||||
'type' => 'list',
|
||||
'title' => 'Choose your language code',
|
||||
'defaultValue' => 'en-US',
|
||||
'values' => array(
|
||||
'es-AR' => 'es-AR',
|
||||
'de-AT' => 'de-AT',
|
||||
'en-AU' => 'en-AU',
|
||||
'nl-BE' => 'nl-BE',
|
||||
'fr-BE' => 'fr-BE',
|
||||
'pt-BR' => 'pt-BR',
|
||||
'en-CA' => 'en-CA',
|
||||
'fr-CA' => 'fr-CA',
|
||||
'de-CH' => 'de-CH',
|
||||
'fr-CH' => 'fr-CH',
|
||||
'es-CL' => 'es-CL',
|
||||
'zh-CN' => 'zh-CN',
|
||||
'es-CO' => 'es-CO',
|
||||
'de-DE' => 'de-DE',
|
||||
'es-ES' => 'es-ES',
|
||||
'fr-FR' => 'fr-FR',
|
||||
'en-GB' => 'en-GB',
|
||||
'en-HK' => 'en-HK',
|
||||
'en-IE' => 'en-IE',
|
||||
'en-IN' => 'en-IN',
|
||||
'it-IT' => 'it-IT',
|
||||
'ja-JP' => 'ja-JP',
|
||||
'ko-KR' => 'ko-KR',
|
||||
'es-MX' => 'es-MX',
|
||||
'nl-NL' => 'nl-NL',
|
||||
'pl-PL' => 'pl-PL',
|
||||
'en-SG' => 'en-SG',
|
||||
'en-US' => 'en-US',
|
||||
'en-ZA' => 'en-ZA',
|
||||
'en-AE' => 'en-AE',
|
||||
'da-DK' => 'da-DK',
|
||||
'in-ID' => 'in-ID',
|
||||
'en-MY' => 'en-MY',
|
||||
'es-PE' => 'es-PE',
|
||||
'en-PH' => 'en-PH',
|
||||
'en-PK' => 'en-PK',
|
||||
'ro-RO' => 'ro-RO',
|
||||
'ru-RU' => 'ru-RU',
|
||||
'tr-TR' => 'tr-TR',
|
||||
'zh-TW' => 'zh-TW',
|
||||
'vi-VN' => 'vi-VN',
|
||||
'en-VN' => 'en-VN',
|
||||
'ar-EG' => 'ar-EG',
|
||||
'fr-MA' => 'fr-MA',
|
||||
'en-NG' => 'en-NG',
|
||||
)
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Maximum number of items to return',
|
||||
'exampleValue' => 20,
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const SITES = array(
|
||||
'es-AR' => 'https://ar.indeed.com/',
|
||||
'de-AT' => 'https://at.indeed.com/',
|
||||
'en-AU' => 'https://au.indeed.com/',
|
||||
'nl-BE' => 'https://be.indeed.com/',
|
||||
'fr-BE' => 'https://emplois.be.indeed.com/',
|
||||
'pt-BR' => 'https://www.indeed.com.br/',
|
||||
'en-CA' => 'https://ca.indeed.com/',
|
||||
'fr-CA' => 'https://emplois.ca.indeed.com/',
|
||||
'de-CH' => 'https://www.indeed.ch/',
|
||||
'fr-CH' => 'https://emplois.indeed.ch/',
|
||||
'es-CL' => 'https://www.indeed.cl/',
|
||||
'zh-CN' => 'https://cn.indeed.com/',
|
||||
'es-CO' => 'https://co.indeed.com/',
|
||||
'de-DE' => 'https://de.indeed.com/',
|
||||
'es-ES' => 'https://www.indeed.es/',
|
||||
'fr-FR' => 'https://www.indeed.fr/',
|
||||
'en-GB' => 'https://www.indeed.co.uk/',
|
||||
'en-HK' => 'https://www.indeed.hk/',
|
||||
'en-IE' => 'https://ie.indeed.com/',
|
||||
'en-IN' => 'https://www.indeed.co.in/',
|
||||
'it-IT' => 'https://it.indeed.com/',
|
||||
'ja-JP' => 'https://jp.indeed.com/',
|
||||
'ko-KR' => 'https://kr.indeed.com/',
|
||||
'es-MX' => 'https://www.indeed.com.mx/',
|
||||
'nl-NL' => 'https://www.indeed.nl/',
|
||||
'pl-PL' => 'https://pl.indeed.com/',
|
||||
'en-SG' => 'https://www.indeed.com.sg/',
|
||||
'en-US' => 'https://www.indeed.com/',
|
||||
'en-ZA' => 'https://www.indeed.co.za/',
|
||||
'en-AE' => 'https://www.indeed.ae/',
|
||||
'da-DK' => 'https://dk.indeed.com/',
|
||||
'in-ID' => 'https://id.indeed.com/',
|
||||
'en-MY' => 'https://www.indeed.com.my/',
|
||||
'es-PE' => 'https://www.indeed.com.pe/',
|
||||
'en-PH' => 'https://www.indeed.com.ph/',
|
||||
'en-PK' => 'https://www.indeed.com.pk/',
|
||||
'ro-RO' => 'https://ro.indeed.com/',
|
||||
'ru-RU' => 'https://ru.indeed.com/',
|
||||
'tr-TR' => 'https://tr.indeed.com/',
|
||||
'zh-TW' => 'https://tw.indeed.com/',
|
||||
'vi-VN' => 'https://vn.indeed.com/',
|
||||
'en-VN' => 'https://jobs.vn.indeed.com/',
|
||||
'ar-EG' => 'https://eg.indeed.com/',
|
||||
'fr-MA' => 'https://ma.indeed.com/',
|
||||
'en-NG' => 'https://ng.indeed.com/',
|
||||
);
|
||||
|
||||
private $title;
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$url = $this->getURI();
|
||||
$limit = $this->getInput('limit') ?: 20;
|
||||
|
||||
do {
|
||||
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request ' . $url);
|
||||
|
||||
$html = defaultLinkTo($html, $url);
|
||||
|
||||
$this->title = $html->find('h1', 0)->innertext;
|
||||
|
||||
// Use local translation of the word "Rating"
|
||||
$rating_local = $html->find('a[data-id="rating_desc"]', 0)->plaintext;
|
||||
|
||||
foreach($html->find('#cmp-content [id^="cmp-review-"]') as $review) {
|
||||
$item = array();
|
||||
|
||||
$rating = $review->find('.cmp-ratingNumber', 0)->plaintext;
|
||||
$title = $review->find('.cmp-review-title > span', 0)->plaintext;
|
||||
$comment = $this->beautifyComment($review->find('.cmp-review-content-container', 0));
|
||||
|
||||
$item['uri'] = $review->find('.cmp-review-share-popup-item-link--copylink', 0)->href;
|
||||
$item['title'] = "{$rating_local} {$rating} / {$title}";
|
||||
$item['timestamp'] = $review->find('.cmp-review-date-created', 0)->plaintext;
|
||||
$item['author'] = $review->find('.cmp-reviewer', 0)->plaintext;
|
||||
$item['content'] = $comment;
|
||||
//$item['enclosures']
|
||||
$item['categories'][] = $review->find('.cmp-reviewer-job-location', 0)->plaintext;
|
||||
//$item['uid']
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if(count($this->items) >= $limit) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Break if no more pages available.
|
||||
if($next = $html->find('a[data-tn-element="next-page"]', 0)) {
|
||||
$url = $next->href;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
} while(count($this->items) < $limit);
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if($this->getInput('language')
|
||||
&& $this->getInput('c')) {
|
||||
return self::SITES[$this->getInput('language')]
|
||||
. 'cmp/'
|
||||
. urlencode($this->getInput('c'))
|
||||
. '/reviews';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
|
||||
public function detectParameters($url) {
|
||||
/**
|
||||
* Expected: https://<...>.indeed.<...>/cmp/<company>[/reviews][/...]
|
||||
*
|
||||
* Note that most users will be redirected to their localized version
|
||||
* of the page, which adds the language code to the host. For example,
|
||||
* "en.indeed.com" or "www.indeed.fr" (see link[rel="alternate"]). At
|
||||
* least each of the sites have ".indeed." in the name.
|
||||
*/
|
||||
|
||||
if(filter_var($url, FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED) === false
|
||||
|| stristr($url, '.indeed.') === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$url_components = parse_url($url);
|
||||
$path_segments = array_values(array_filter(explode('/', $url_components['path'])));
|
||||
|
||||
if(count($path_segments) < 2 || $path_segments[0] !== 'cmp') {
|
||||
return null;
|
||||
}
|
||||
|
||||
$language = array_search('https://' . $url_components['host'] . '/', self::SITES);
|
||||
if($language === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$limit = self::PARAMETERS['global']['limit']['defaultValue'] ?: 20;
|
||||
$company = $path_segments[1];
|
||||
|
||||
return array(
|
||||
'c' => $company,
|
||||
'language' => $language,
|
||||
'limit' => $limit,
|
||||
);
|
||||
}
|
||||
|
||||
private function beautifyComment($comment) {
|
||||
foreach($comment->find('.cmp-bold') as $bold) {
|
||||
$bold->tag = 'strong';
|
||||
$bold->removeClass('cmp-bold');
|
||||
}
|
||||
|
||||
return $comment;
|
||||
}
|
||||
}
|
@@ -32,28 +32,61 @@ class InstagramBridge extends BridgeAbstract {
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Story' => 'story',
|
||||
'Video' => 'video',
|
||||
'Picture' => 'picture',
|
||||
'Multiple' => 'multiple',
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
),
|
||||
'direct_links' => array(
|
||||
'name' => 'Use direct media links',
|
||||
'type' => 'checkbox',
|
||||
)
|
||||
)
|
||||
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
const USER_QUERY_HASH = '58b6785bea111c67129decbe6a448951';
|
||||
const TAG_QUERY_HASH = '9b498c08113f1e09617a1703c22b2f32';
|
||||
const SHORTCODE_QUERY_HASH = '865589822932d1b43dfe312121dd353a';
|
||||
|
||||
if(is_null($this->getInput('u')) && $this->getInput('media_type') == 'story') {
|
||||
returnClientError('Stories are not supported for hashtags nor locations!');
|
||||
protected function getInstagramUserId($username) {
|
||||
|
||||
if(is_numeric($username)) return $username;
|
||||
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope(get_called_class());
|
||||
$cache->setKey(array($username));
|
||||
$key = $cache->loadData();
|
||||
|
||||
if($key == null) {
|
||||
$data = getContents(self::URI . 'web/search/topsearch/?query=' . $username);
|
||||
|
||||
foreach(json_decode($data)->users as $user) {
|
||||
if(strtolower($user->user->username) === strtolower($username)) {
|
||||
$key = $user->user->pk;
|
||||
}
|
||||
}
|
||||
if($key == null) {
|
||||
returnServerError('Unable to find username in search result.');
|
||||
}
|
||||
$cache->saveData($key);
|
||||
}
|
||||
return $key;
|
||||
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$directLink = !is_null($this->getInput('direct_links')) && $this->getInput('direct_links');
|
||||
|
||||
$data = $this->getInstagramJSON($this->getURI());
|
||||
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
$userMedia = $data->entry_data->ProfilePage[0]->graphql->user->edge_owner_to_timeline_media->edges;
|
||||
$userMedia = $data->data->user->edge_owner_to_timeline_media->edges;
|
||||
} elseif(!is_null($this->getInput('h'))) {
|
||||
$userMedia = $data->entry_data->TagPage[0]->graphql->hashtag->edge_hashtag_to_media->edges;
|
||||
$userMedia = $data->data->hashtag->edge_hashtag_to_media->edges;
|
||||
} elseif(!is_null($this->getInput('l'))) {
|
||||
$userMedia = $data->entry_data->LocationsPage[0]->graphql->location->edge_location_to_media->edges;
|
||||
}
|
||||
@@ -61,22 +94,18 @@ class InstagramBridge extends BridgeAbstract {
|
||||
foreach($userMedia as $media) {
|
||||
$media = $media->node;
|
||||
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
switch($this->getInput('media_type')) {
|
||||
case 'all': break;
|
||||
case 'video':
|
||||
if($media->__typename != 'GraphVideo') continue 2;
|
||||
break;
|
||||
case 'picture':
|
||||
if($media->__typename != 'GraphImage') continue 2;
|
||||
break;
|
||||
case 'story':
|
||||
if($media->__typename != 'GraphSidecar') continue 2;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
} else {
|
||||
if($this->getInput('media_type') == 'video' && !$media->is_video) continue;
|
||||
switch($this->getInput('media_type')) {
|
||||
case 'all': break;
|
||||
case 'video':
|
||||
if($media->__typename != 'GraphVideo' || !$media->is_video) continue 2;
|
||||
break;
|
||||
case 'picture':
|
||||
if($media->__typename != 'GraphImage') continue 2;
|
||||
break;
|
||||
case 'multiple':
|
||||
if($media->__typename != 'GraphSidecar') continue 2;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
@@ -86,72 +115,146 @@ class InstagramBridge extends BridgeAbstract {
|
||||
$item['author'] = $media->owner->username;
|
||||
}
|
||||
|
||||
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
||||
$textContent = $media->edge_media_to_caption->edges[0]->node->text;
|
||||
} else {
|
||||
$textContent = '(no text)';
|
||||
}
|
||||
$textContent = $this->getTextContent($media);
|
||||
|
||||
$item['title'] = ($media->is_video ? '▶ ' : '') . trim($textContent);
|
||||
$item['title'] = ($media->is_video ? '▶ ' : '') . $textContent;
|
||||
$titleLinePos = strpos(wordwrap($item['title'], 120), "\n");
|
||||
if ($titleLinePos != false) {
|
||||
$item['title'] = substr($item['title'], 0, $titleLinePos) . '...';
|
||||
}
|
||||
|
||||
if(!is_null($this->getInput('u')) && $media->__typename == 'GraphSidecar') {
|
||||
$data = $this->getInstagramStory($item['uri']);
|
||||
$item['content'] = $data[0];
|
||||
$item['enclosures'] = $data[1];
|
||||
if($directLink) {
|
||||
$mediaURI = $media->display_url;
|
||||
} else {
|
||||
$mediaURI = self::URI . 'p/' . $media->shortcode . '/media?size=l';
|
||||
$item['content'] = '<a href="' . htmlentities($item['uri']) . '" target="_blank">';
|
||||
$item['content'] .= '<img src="' . htmlentities($mediaURI) . '" alt="' . $item['title'] . '" />';
|
||||
$item['content'] .= '</a><br><br>' . nl2br(htmlentities($textContent));
|
||||
$item['enclosures'] = array($mediaURI);
|
||||
}
|
||||
|
||||
switch($media->__typename) {
|
||||
case 'GraphSidecar':
|
||||
$data = $this->getInstagramSidecarData($item['uri'], $item['title']);
|
||||
$item['content'] = $data[0];
|
||||
$item['enclosures'] = $data[1];
|
||||
break;
|
||||
case 'GraphImage':
|
||||
$item['content'] = '<a href="' . htmlentities($item['uri']) . '" target="_blank">';
|
||||
$item['content'] .= '<img src="' . htmlentities($mediaURI) . '" alt="' . $item['title'] . '" />';
|
||||
$item['content'] .= '</a><br><br>' . nl2br(htmlentities($textContent));
|
||||
$item['enclosures'] = array($mediaURI);
|
||||
break;
|
||||
case 'GraphVideo':
|
||||
$data = $this->getInstagramVideoData($item['uri'], $mediaURI);
|
||||
$item['content'] = $data[0];
|
||||
if($directLink) {
|
||||
$item['enclosures'] = $data[1];
|
||||
} else {
|
||||
$item['enclosures'] = array($mediaURI);
|
||||
}
|
||||
$item['thumbnail'] = $mediaURI;
|
||||
break;
|
||||
default: break;
|
||||
}
|
||||
$item['timestamp'] = $media->taken_at_timestamp;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
protected function getInstagramStory($uri) {
|
||||
// returns Sidecar(a post which has multiple media)'s contents and enclosures
|
||||
protected function getInstagramSidecarData($uri, $postTitle) {
|
||||
$mediaInfo = $this->getSinglePostData($uri);
|
||||
|
||||
$data = $this->getInstagramJSON($uri);
|
||||
$mediaInfo = $data->entry_data->PostPage[0]->graphql->shortcode_media;
|
||||
$textContent = $this->getTextContent($mediaInfo);
|
||||
|
||||
//Process the first element, that isn't in the node graph
|
||||
if (count($mediaInfo->edge_media_to_caption->edges) > 0) {
|
||||
$caption = $mediaInfo->edge_media_to_caption->edges[0]->node->text;
|
||||
} else {
|
||||
$caption = '';
|
||||
}
|
||||
|
||||
$enclosures = [$mediaInfo->display_url];
|
||||
$content = '<img src="' . htmlentities($mediaInfo->display_url) . '" alt="' . $caption . '" />';
|
||||
|
||||
foreach($mediaInfo->edge_sidecar_to_children->edges as $media) {
|
||||
$display_url = $media->node->display_url;
|
||||
if(!in_array($display_url, $enclosures)) { // add only if not added yet
|
||||
$content .= '<img src="' . htmlentities($display_url) . '" alt="' . $caption . '" />';
|
||||
$enclosures[] = $display_url;
|
||||
$enclosures = array();
|
||||
$content = '';
|
||||
foreach($mediaInfo->edge_sidecar_to_children->edges as $singleMedia) {
|
||||
$singleMedia = $singleMedia->node;
|
||||
if($singleMedia->is_video) {
|
||||
if(in_array($singleMedia->video_url, $enclosures)) continue; // check if not added yet
|
||||
$content .= '<video controls><source src="' . $singleMedia->video_url . '" type="video/mp4"></video><br>';
|
||||
array_push($enclosures, $singleMedia->video_url);
|
||||
} else {
|
||||
if(in_array($singleMedia->display_url, $enclosures)) continue; // check if not added yet
|
||||
$content .= '<a href="' . $singleMedia->display_url . '" target="_blank">';
|
||||
$content .= '<img src="' . $singleMedia->display_url . '" alt="' . $postTitle . '" />';
|
||||
$content .= '</a><br>';
|
||||
array_push($enclosures, $singleMedia->display_url);
|
||||
}
|
||||
}
|
||||
$content .= '<br>' . nl2br(htmlentities($textContent));
|
||||
|
||||
return [$content, $enclosures];
|
||||
return array($content, $enclosures);
|
||||
}
|
||||
|
||||
// returns Video post's contents and enclosures
|
||||
protected function getInstagramVideoData($uri, $mediaURI) {
|
||||
$mediaInfo = $this->getSinglePostData($uri);
|
||||
|
||||
$textContent = $this->getTextContent($mediaInfo);
|
||||
$content = '<video controls>';
|
||||
$content .= '<source src="' . $mediaInfo->video_url . '" poster="' . $mediaURI . '" type="video/mp4">';
|
||||
$content .= '<img src="' . $mediaURI . '" alt="">';
|
||||
$content .= '</video><br>';
|
||||
$content .= '<br>' . nl2br(htmlentities($textContent));
|
||||
|
||||
return array($content, array($mediaInfo->video_url));
|
||||
}
|
||||
|
||||
protected function getTextContent($media) {
|
||||
$textContent = '(no text)';
|
||||
//Process the first element, that isn't in the node graph
|
||||
if (count($media->edge_media_to_caption->edges) > 0) {
|
||||
$textContent = trim($media->edge_media_to_caption->edges[0]->node->text);
|
||||
}
|
||||
return $textContent;
|
||||
}
|
||||
|
||||
protected function getSinglePostData($uri) {
|
||||
$shortcode = explode('/', $uri)[4];
|
||||
$data = getContents(self::URI .
|
||||
'graphql/query/?query_hash=' .
|
||||
self::SHORTCODE_QUERY_HASH .
|
||||
'&variables={"shortcode"%3A"' .
|
||||
$shortcode .
|
||||
'"}');
|
||||
|
||||
return json_decode($data)->data->shortcode_media;
|
||||
}
|
||||
|
||||
protected function getInstagramJSON($uri) {
|
||||
|
||||
$html = getContents($uri)
|
||||
or returnServerError('Could not request Instagram.');
|
||||
$scriptRegex = '/window\._sharedData = (.*);<\/script>/';
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
|
||||
preg_match($scriptRegex, $html, $matches, PREG_OFFSET_CAPTURE, 0);
|
||||
$userId = $this->getInstagramUserId($this->getInput('u'));
|
||||
|
||||
return json_decode($matches[1][0]);
|
||||
$data = getContents(self::URI .
|
||||
'graphql/query/?query_hash=' .
|
||||
self::USER_QUERY_HASH .
|
||||
'&variables={"id"%3A"' .
|
||||
$userId .
|
||||
'"%2C"first"%3A10}');
|
||||
return json_decode($data);
|
||||
|
||||
} elseif(!is_null($this->getInput('h'))) {
|
||||
$data = getContents(self::URI .
|
||||
'graphql/query/?query_hash=' .
|
||||
self::TAG_QUERY_HASH .
|
||||
'&variables={"tag_name"%3A"' .
|
||||
$this->getInput('h') .
|
||||
'"%2C"first"%3A10}');
|
||||
return json_decode($data);
|
||||
|
||||
} else {
|
||||
|
||||
$html = getContents($uri)
|
||||
or returnServerError('Could not request Instagram.');
|
||||
$scriptRegex = '/window\._sharedData = (.*);<\/script>/';
|
||||
|
||||
preg_match($scriptRegex, $html, $matches, PREG_OFFSET_CAPTURE, 0);
|
||||
|
||||
return json_decode($matches[1][0]);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
@@ -1,8 +1,7 @@
|
||||
<?php
|
||||
/**
|
||||
* This class implements a bridge for http://www.instructables.com, supporting
|
||||
* general feeds and feeds by category. Instructables doesn't support HTTPS as
|
||||
* of now (23.06.2018), so all connections are insecure!
|
||||
* general feeds and feeds by category.
|
||||
*
|
||||
* Remarks:
|
||||
* - For some reason it is very important to have the category URI end with a
|
||||
@@ -13,7 +12,7 @@
|
||||
*/
|
||||
class InstructablesBridge extends BridgeAbstract {
|
||||
const NAME = 'Instructables Bridge';
|
||||
const URI = 'http://www.instructables.com';
|
||||
const URI = 'https://www.instructables.com';
|
||||
const DESCRIPTION = 'Returns general feeds and feeds by category';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const PARAMETERS = array(
|
||||
@@ -22,219 +21,201 @@ class InstructablesBridge extends BridgeAbstract {
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Play' => array(
|
||||
'All' => '/play/',
|
||||
'KNEX' => '/play/knex/',
|
||||
'Offbeat' => '/play/offbeat/',
|
||||
'Lego' => '/play/lego/',
|
||||
'Airsoft' => '/play/airsoft/',
|
||||
'Card Games' => '/play/card-games/',
|
||||
'Guitars' => '/play/guitars/',
|
||||
'Instruments' => '/play/instruments/',
|
||||
'Magic Tricks' => '/play/magic-tricks/',
|
||||
'Minecraft' => '/play/minecraft/',
|
||||
'Music' => '/play/music/',
|
||||
'Nerf' => '/play/nerf/',
|
||||
'Nintendo' => '/play/nintendo/',
|
||||
'Office Supplies' => '/play/office-supplies/',
|
||||
'Paintball' => '/play/paintball/',
|
||||
'Paper Airplanes' => '/play/paper-airplanes/',
|
||||
'Party Tricks' => '/play/party-tricks/',
|
||||
'PlayStation' => '/play/playstation/',
|
||||
'Pranks and Humor' => '/play/pranks-and-humor/',
|
||||
'Puzzles' => '/play/puzzles/',
|
||||
'Siege Engines' => '/play/siege-engines/',
|
||||
'Sports' => '/play/sports/',
|
||||
'Table Top' => '/play/table-top/',
|
||||
'Toys' => '/play/toys/',
|
||||
'Video Games' => '/play/video-games/',
|
||||
'Wii' => '/play/wii/',
|
||||
'Xbox' => '/play/xbox/',
|
||||
'Yo-Yo' => '/play/yo-yo/',
|
||||
),
|
||||
'Craft' => array(
|
||||
'All' => '/craft/',
|
||||
'Art' => '/craft/art/',
|
||||
'Sewing' => '/craft/sewing/',
|
||||
'Paper' => '/craft/paper/',
|
||||
'Jewelry' => '/craft/jewelry/',
|
||||
'Fashion' => '/craft/fashion/',
|
||||
'Books & Journals' => '/craft/books-and-journals/',
|
||||
'Cards' => '/craft/cards/',
|
||||
'Clay' => '/craft/clay/',
|
||||
'Duct Tape' => '/craft/duct-tape/',
|
||||
'Embroidery' => '/craft/embroidery/',
|
||||
'Felt' => '/craft/felt/',
|
||||
'Fiber Arts' => '/craft/fiber-arts/',
|
||||
'Gifts & Wrapping' => '/craft/gifts-and-wrapping/',
|
||||
'Knitting & Crocheting' => '/craft/knitting-and-crocheting/',
|
||||
'Leather' => '/craft/leather/',
|
||||
'Mason Jars' => '/craft/mason-jars/',
|
||||
'No-Sew' => '/craft/no-sew/',
|
||||
'Parties & Weddings' => '/craft/parties-and-weddings/',
|
||||
'Print Making' => '/craft/print-making/',
|
||||
'Soap' => '/craft/soap/',
|
||||
'Wallets' => '/craft/wallets/',
|
||||
),
|
||||
'Technology' => array(
|
||||
'All' => '/technology/',
|
||||
'Electronics' => '/technology/electronics/',
|
||||
'Arduino' => '/technology/arduino/',
|
||||
'Photography' => '/technology/photography/',
|
||||
'Leds' => '/technology/leds/',
|
||||
'Science' => '/technology/science/',
|
||||
'Reuse' => '/technology/reuse/',
|
||||
'Apple' => '/technology/apple/',
|
||||
'Computers' => '/technology/computers/',
|
||||
'3D Printing' => '/technology/3D-Printing/',
|
||||
'Robots' => '/technology/robots/',
|
||||
'Art' => '/technology/art/',
|
||||
'Assistive Tech' => '/technology/assistive-technology/',
|
||||
'Audio' => '/technology/audio/',
|
||||
'Clocks' => '/technology/clocks/',
|
||||
'CNC' => '/technology/cnc/',
|
||||
'Digital Graphics' => '/technology/digital-graphics/',
|
||||
'Gadgets' => '/technology/gadgets/',
|
||||
'Kits' => '/technology/kits/',
|
||||
'Laptops' => '/technology/laptops/',
|
||||
'Lasers' => '/technology/lasers/',
|
||||
'Linux' => '/technology/linux/',
|
||||
'Microcontrollers' => '/technology/microcontrollers/',
|
||||
'Microsoft' => '/technology/microsoft/',
|
||||
'Mobile' => '/technology/mobile/',
|
||||
'Raspberry Pi' => '/technology/raspberry-pi/',
|
||||
'Remote Control' => '/technology/remote-control/',
|
||||
'Sensors' => '/technology/sensors/',
|
||||
'Software' => '/technology/software/',
|
||||
'Soldering' => '/technology/soldering/',
|
||||
'Speakers' => '/technology/speakers/',
|
||||
'Steampunk' => '/technology/steampunk/',
|
||||
'Tools' => '/technology/tools/',
|
||||
'USB' => '/technology/usb/',
|
||||
'Wearables' => '/technology/wearables/',
|
||||
'Websites' => '/technology/websites/',
|
||||
'Wireless' => '/technology/wireless/',
|
||||
'Circuits' => array(
|
||||
'All' => '/circuits/',
|
||||
'Apple' => '/circuits/apple/projects/',
|
||||
'Arduino' => '/circuits/arduino/projects/',
|
||||
'Art' => '/circuits/art/projects/',
|
||||
'Assistive Tech' => '/circuits/assistive-tech/projects/',
|
||||
'Audio' => '/circuits/audio/projects/',
|
||||
'Cameras' => '/circuits/cameras/projects/',
|
||||
'Clocks' => '/circuits/clocks/projects/',
|
||||
'Computers' => '/circuits/computers/projects/',
|
||||
'Electronics' => '/circuits/electronics/projects/',
|
||||
'Gadgets' => '/circuits/gadgets/projects/',
|
||||
'Lasers' => '/circuits/lasers/projects/',
|
||||
'LEDs' => '/circuits/leds/projects/',
|
||||
'Linux' => '/circuits/linux/projects/',
|
||||
'Microcontrollers' => '/circuits/microcontrollers/projects/',
|
||||
'Microsoft' => '/circuits/microsoft/projects/',
|
||||
'Mobile' => '/circuits/mobile/projects/',
|
||||
'Raspberry Pi' => '/circuits/raspberry-pi/projects/',
|
||||
'Remote Control' => '/circuits/remote-control/projects/',
|
||||
'Reuse' => '/circuits/reuse/projects/',
|
||||
'Robots' => '/circuits/robots/projects/',
|
||||
'Sensors' => '/circuits/sensors/projects/',
|
||||
'Software' => '/circuits/software/projects/',
|
||||
'Soldering' => '/circuits/soldering/projects/',
|
||||
'Speakers' => '/circuits/speakers/projects/',
|
||||
'Tools' => '/circuits/tools/projects/',
|
||||
'USB' => '/circuits/usb/projects/',
|
||||
'Wearables' => '/circuits/wearables/projects/',
|
||||
'Websites' => '/circuits/websites/projects/',
|
||||
'Wireless' => '/circuits/wireless/projects/',
|
||||
),
|
||||
'Workshop' => array(
|
||||
'All' => '/workshop/',
|
||||
'Woodworking' => '/workshop/woodworking/',
|
||||
'Tools' => '/workshop/tools/',
|
||||
'Gardening' => '/workshop/gardening/',
|
||||
'Cars' => '/workshop/cars/',
|
||||
'Metalworking' => '/workshop/metalworking/',
|
||||
'Cardboard' => '/workshop/cardboard/',
|
||||
'Electric Vehicles' => '/workshop/electric-vehicles/',
|
||||
'Energy' => '/workshop/energy/',
|
||||
'Furniture' => '/workshop/furniture/',
|
||||
'Home Improvement' => '/workshop/home-improvement/',
|
||||
'Home Theater' => '/workshop/home-theater/',
|
||||
'Hydroponics' => '/workshop/hydroponics/',
|
||||
'Laser Cutting' => '/workshop/laser-cutting/',
|
||||
'Lighting' => '/workshop/lighting/',
|
||||
'Molds & Casting' => '/workshop/molds-and-casting/',
|
||||
'Motorcycles' => '/workshop/motorcycles/',
|
||||
'Organizing' => '/workshop/organizing/',
|
||||
'Pallets' => '/workshop/pallets/',
|
||||
'Repair' => '/workshop/repair/',
|
||||
'Shelves' => '/workshop/shelves/',
|
||||
'Solar' => '/workshop/solar/',
|
||||
'Workbenches' => '/workshop/workbenches/',
|
||||
'3D Printing' => '/workshop/3d-printing/projects/',
|
||||
'Cars' => '/workshop/cars/projects/',
|
||||
'CNC' => '/workshop/cnc/projects/',
|
||||
'Electric Vehicles' => '/workshop/electric-vehicles/projects/',
|
||||
'Energy' => '/workshop/energy/projects/',
|
||||
'Furniture' => '/workshop/furniture/projects/',
|
||||
'Home Improvement' => '/workshop/home-improvement/projects/',
|
||||
'Home Theater' => '/workshop/home-theater/projects/',
|
||||
'Hydroponics' => '/workshop/hydroponics/projects/',
|
||||
'Knives' => '/workshop/knives/projects/',
|
||||
'Laser Cutting' => '/workshop/laser-cutting/projects/',
|
||||
'Lighting' => '/workshop/lighting/projects/',
|
||||
'Metalworking' => '/workshop/metalworking/projects/',
|
||||
'Molds & Casting' => '/workshop/molds-and-casting/projects/',
|
||||
'Motorcycles' => '/workshop/motorcycles/projects/',
|
||||
'Organizing' => '/workshop/organizing/projects/',
|
||||
'Pallets' => '/workshop/pallets/projects/',
|
||||
'Repair' => '/workshop/repair/projects/',
|
||||
'Science' => '/workshop/science/projects/',
|
||||
'Shelves' => '/workshop/shelves/projects/',
|
||||
'Solar' => '/workshop/solar/projects/',
|
||||
'Tools' => '/workshop/tools/projects/',
|
||||
'Woodworking' => '/workshop/woodworking/projects/',
|
||||
'Workbenches' => '/workshop/workbenches/projects/',
|
||||
),
|
||||
'Home' => array(
|
||||
'All' => '/home/',
|
||||
'Halloween' => '/home/halloween/',
|
||||
'Decorating' => '/home/decorating/',
|
||||
'Organizing' => '/home/organizing/',
|
||||
'Pets' => '/home/pets/',
|
||||
'Life Hacks' => '/home/life-hacks/',
|
||||
'Beauty' => '/home/beauty/',
|
||||
'Christmas' => '/home/christmas/',
|
||||
'Cleaning' => '/home/cleaning/',
|
||||
'Education' => '/home/education/',
|
||||
'Finances' => '/home/finances/',
|
||||
'Gardening' => '/home/gardening/',
|
||||
'Green' => '/home/green/',
|
||||
'Health' => '/home/health/',
|
||||
'Hiding Places' => '/home/hiding-places/',
|
||||
'Holidays' => '/home/holidays/',
|
||||
'Homesteading' => '/home/homesteading/',
|
||||
'Kids' => '/home/kids/',
|
||||
'Kitchen' => '/home/kitchen/',
|
||||
'Life Skills' => '/home/life-skills/',
|
||||
'Parenting' => '/home/parenting/',
|
||||
'Pest Control' => '/home/pest-control/',
|
||||
'Relationships' => '/home/relationships/',
|
||||
'Reuse' => '/home/reuse/',
|
||||
'Travel' => '/home/travel/',
|
||||
'Craft' => array(
|
||||
'All' => '/craft/',
|
||||
'Art' => '/craft/art/projects/',
|
||||
'Books & Journals' => '/craft/books-and-journals/projects/',
|
||||
'Cardboard' => '/craft/cardboard/projects/',
|
||||
'Cards' => '/craft/cards/projects/',
|
||||
'Clay' => '/craft/clay/projects/',
|
||||
'Costumes & Cosplay' => '/craft/costumes-and-cosplay/projects/',
|
||||
'Digital Graphics' => '/craft/digital-graphics/projects/',
|
||||
'Duct Tape' => '/craft/duct-tape/projects/',
|
||||
'Embroidery' => '/craft/embroidery/projects/',
|
||||
'Fashion' => '/craft/fashion/projects/',
|
||||
'Felt' => '/craft/felt/projects/',
|
||||
'Fiber Arts' => '/craft/fiber-arts/projects/',
|
||||
'Gift Wrapping' => '/craft/gift-wrapping/projects/',
|
||||
'Jewelry' => '/craft/jewelry/projects/',
|
||||
'Knitting & Crochet' => '/craft/knitting-and-crochet/projects/',
|
||||
'Leather' => '/craft/leather/projects/',
|
||||
'Mason Jars' => '/craft/mason-jars/projects/',
|
||||
'No-Sew' => '/craft/no-sew/projects/',
|
||||
'Paper' => '/craft/paper/projects/',
|
||||
'Parties & Weddings' => '/craft/parties-and-weddings/projects/',
|
||||
'Photography' => '/craft/photography/projects/',
|
||||
'Printmaking' => '/craft/printmaking/projects/',
|
||||
'Reuse' => '/craft/reuse/projects/',
|
||||
'Sewing' => '/craft/sewing/projects/',
|
||||
'Soapmaking' => '/craft/soapmaking/projects/',
|
||||
'Wallets' => '/craft/wallets/projects/',
|
||||
),
|
||||
'Cooking' => array(
|
||||
'All' => '/cooking/',
|
||||
'Bacon' => '/cooking/bacon/projects/',
|
||||
'BBQ & Grilling' => '/cooking/bbq-and-grilling/projects/',
|
||||
'Beverages' => '/cooking/beverages/projects/',
|
||||
'Bread' => '/cooking/bread/projects/',
|
||||
'Breakfast' => '/cooking/breakfast/projects/',
|
||||
'Cake' => '/cooking/cake/projects/',
|
||||
'Candy' => '/cooking/candy/projects/',
|
||||
'Canning & Preserving' => '/cooking/canning-and-preserving/projects/',
|
||||
'Cocktails & Mocktails' => '/cooking/cocktails-and-mocktails/projects/',
|
||||
'Coffee' => '/cooking/coffee/projects/',
|
||||
'Cookies' => '/cooking/cookies/projects/',
|
||||
'Cupcakes' => '/cooking/cupcakes/projects/',
|
||||
'Dessert' => '/cooking/dessert/projects/',
|
||||
'Homebrew' => '/cooking/homebrew/projects/',
|
||||
'Main Course' => '/cooking/main-course/projects/',
|
||||
'Pasta' => '/cooking/pasta/projects/',
|
||||
'Pie' => '/cooking/pie/projects/',
|
||||
'Pizza' => '/cooking/pizza/projects/',
|
||||
'Salad' => '/cooking/salad/projects/',
|
||||
'Sandwiches' => '/cooking/sandwiches/projects/',
|
||||
'Snacks & Appetizers' => '/cooking/snacks-and-appetizers/projects/',
|
||||
'Soups & Stews' => '/cooking/soups-and-stews/projects/',
|
||||
'Vegetarian & Vegan' => '/cooking/vegetarian-and-vegan/projects/',
|
||||
),
|
||||
'Living' => array(
|
||||
'All' => '/living/',
|
||||
'Beauty' => '/living/beauty/projects/',
|
||||
'Christmas' => '/living/christmas/projects/',
|
||||
'Cleaning' => '/living/cleaning/projects/',
|
||||
'Decorating' => '/living/decorating/projects/',
|
||||
'Education' => '/living/education/projects/',
|
||||
'Gardening' => '/living/gardening/projects/',
|
||||
'Halloween' => '/living/halloween/projects/',
|
||||
'Health' => '/living/health/projects/',
|
||||
'Hiding Places' => '/living/hiding-places/projects/',
|
||||
'Holidays' => '/living/holidays/projects/',
|
||||
'Homesteading' => '/living/homesteading/projects/',
|
||||
'Kids' => '/living/kids/projects/',
|
||||
'Kitchen' => '/living/kitchen/projects/',
|
||||
'LEGO & KNEX' => '/living/lego-and-knex/projects/',
|
||||
'Life Hacks' => '/living/life-hacks/projects/',
|
||||
'Music' => '/living/music/projects/',
|
||||
'Office Supply Hacks' => '/living/office-supply-hacks/projects/',
|
||||
'Organizing' => '/living/organizing/projects/',
|
||||
'Pest Control' => '/living/pest-control/projects/',
|
||||
'Pets' => '/living/pets/projects/',
|
||||
'Pranks, Tricks, & Humor' => '/living/pranks-tricks-and-humor/projects/',
|
||||
'Relationships' => '/living/relationships/projects/',
|
||||
'Toys & Games' => '/living/toys-and-games/projects/',
|
||||
'Travel' => '/living/travel/projects/',
|
||||
'Video Games' => '/living/video-games/projects/',
|
||||
),
|
||||
'Outside' => array(
|
||||
'All' => '/outside/',
|
||||
'Bikes' => '/outside/bikes/',
|
||||
'Survival' => '/outside/survival/',
|
||||
'Backyard' => '/outside/backyard/',
|
||||
'Beach' => '/outside/beach/',
|
||||
'Birding' => '/outside/birding/',
|
||||
'Boats' => '/outside/boats/',
|
||||
'Camping' => '/outside/camping/',
|
||||
'Climbing' => '/outside/climbing/',
|
||||
'Fire' => '/outside/fire/',
|
||||
'Fishing' => '/outside/fishing/',
|
||||
'Hunting' => '/outside/hunting/',
|
||||
'Kites' => '/outside/kites/',
|
||||
'Knives' => '/outside/knives/',
|
||||
'Knots' => '/outside/knots/',
|
||||
'Paracord' => '/outside/paracord/',
|
||||
'Rockets' => '/outside/rockets/',
|
||||
'Skateboarding' => '/outside/skateboarding/',
|
||||
'Snow' => '/outside/snow/',
|
||||
'Water' => '/outside/water/',
|
||||
'Backyard' => '/outside/backyard/projects/',
|
||||
'Beach' => '/outside/beach/projects/',
|
||||
'Bikes' => '/outside/bikes/projects/',
|
||||
'Birding' => '/outside/birding/projects/',
|
||||
'Boats' => '/outside/boats/projects/',
|
||||
'Camping' => '/outside/camping/projects/',
|
||||
'Climbing' => '/outside/climbing/projects/',
|
||||
'Fire' => '/outside/fire/projects/',
|
||||
'Fishing' => '/outside/fishing/projects/',
|
||||
'Hunting' => '/outside/hunting/projects/',
|
||||
'Kites' => '/outside/kites/projects/',
|
||||
'Knots' => '/outside/knots/projects/',
|
||||
'Launchers' => '/outside/launchers/projects/',
|
||||
'Paracord' => '/outside/paracord/projects/',
|
||||
'Rockets' => '/outside/rockets/projects/',
|
||||
'Siege Engines' => '/outside/siege-engines/projects/',
|
||||
'Skateboarding' => '/outside/skateboarding/projects/',
|
||||
'Snow' => '/outside/snow/projects/',
|
||||
'Sports' => '/outside/sports/projects/',
|
||||
'Survival' => '/outside/survival/projects/',
|
||||
'Water' => '/outside/water/projects/',
|
||||
),
|
||||
'Food' => array(
|
||||
'All' => '/food/',
|
||||
'Dessert' => '/food/dessert/',
|
||||
'Snacks & Appetizers' => '/food/snacks-and-appetizers/',
|
||||
'Bacon' => '/food/bacon/',
|
||||
'BBQ & Grilling' => '/food/bbq-and-grilling/',
|
||||
'Beverages' => '/food/beverages/',
|
||||
'Bread' => '/food/bread/',
|
||||
'Breakfast' => '/food/breakfast/',
|
||||
'Cake' => '/food/cake/',
|
||||
'Candy' => '/food/candy/',
|
||||
'Canning & Preserves' => '/food/canning-and-preserves/',
|
||||
'Cocktails & Mocktails' => '/food/cocktails-and-mocktails/',
|
||||
'Coffee' => '/food/coffee/',
|
||||
'Cookies' => '/food/cookies/',
|
||||
'Cupcakes' => '/food/cupcakes/',
|
||||
'Homebrew' => '/food/homebrew/',
|
||||
'Main Course' => '/food/main-course/',
|
||||
'Pasta' => '/food/pasta/',
|
||||
'Pie' => '/food/pie/',
|
||||
'Pizza' => '/food/pizza/',
|
||||
'Salad' => '/food/salad/',
|
||||
'Sandwiches' => '/food/sandwiches/',
|
||||
'Soups & Stews' => '/food/soups-and-stews/',
|
||||
'Vegetarian & Vegan' => '/food/vegetarian-and-vegan/',
|
||||
'Makeymakey' => array(
|
||||
'All' => '/makeymakey/',
|
||||
'Makey Makey on Instructables' => '/makeymakey/',
|
||||
),
|
||||
'Teachers' => array(
|
||||
'All' => '/teachers/',
|
||||
'ELA' => '/teachers/ela/projects/',
|
||||
'Math' => '/teachers/math/projects/',
|
||||
'Science' => '/teachers/science/projects/',
|
||||
'Social Studies' => '/teachers/social-studies/projects/',
|
||||
'Engineering' => '/teachers/engineering/projects/',
|
||||
'Coding' => '/teachers/coding/projects/',
|
||||
'Electronics' => '/teachers/electronics/projects/',
|
||||
'Robotics' => '/teachers/robotics/projects/',
|
||||
'Arduino' => '/teachers/arduino/projects/',
|
||||
'CNC' => '/teachers/cnc/projects/',
|
||||
'Laser Cutting' => '/teachers/laser-cutting/projects/',
|
||||
'3D Printing' => '/teachers/3d-printing/projects/',
|
||||
'3D Design' => '/teachers/3d-design/projects/',
|
||||
'Art' => '/teachers/art/projects/',
|
||||
'Music' => '/teachers/music/projects/',
|
||||
'Theatre' => '/teachers/theatre/projects/',
|
||||
'Wood Shop' => '/teachers/wood-shop/projects/',
|
||||
'Metal Shop' => '/teachers/metal-shop/projects/',
|
||||
'Resources' => '/teachers/resources/projects/',
|
||||
),
|
||||
'Costumes' => array(
|
||||
'All' => '/costumes/',
|
||||
'Props' => '/costumes/props-and-accessories/',
|
||||
'Animals' => '/costumes/animals/',
|
||||
'Comics' => '/costumes/comics/',
|
||||
'Fantasy' => '/costumes/fantasy/',
|
||||
'For Kids' => '/costumes/for-kids/',
|
||||
'For Pets' => '/costumes/for-pets/',
|
||||
'Funny' => '/costumes/funny/',
|
||||
'Games' => '/costumes/games/',
|
||||
'Historic & Futuristic' => '/costumes/historic-and-futuristic/',
|
||||
'Makeup' => '/costumes/makeup/',
|
||||
'Masks' => '/costumes/masks/',
|
||||
'Scary' => '/costumes/scary/',
|
||||
'TV & Movies' => '/costumes/tv-and-movies/',
|
||||
'Weapons & Armor' => '/costumes/weapons-and-armor/',
|
||||
)
|
||||
),
|
||||
'title' => 'Select your category (required)',
|
||||
'defaultValue' => 'Technology'
|
||||
'defaultValue' => 'Circuits'
|
||||
),
|
||||
'filter' => array(
|
||||
'name' => 'Filter',
|
||||
@@ -252,65 +233,70 @@ class InstructablesBridge extends BridgeAbstract {
|
||||
)
|
||||
);
|
||||
|
||||
private $uri;
|
||||
|
||||
public function collectData() {
|
||||
// Enable the following line to get the category list (dev mode)
|
||||
// $this->listCategories();
|
||||
|
||||
$this->uri = static::URI;
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Error loading category ' . $this->getURI());
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Category': $this->uri .= $this->getInput('category') . $this->getInput('filter');
|
||||
}
|
||||
$covers = $html->find('
|
||||
.category-projects-list > div,
|
||||
.category-landing-projects-list > div,
|
||||
');
|
||||
|
||||
$html = getSimpleHTMLDOM($this->uri)
|
||||
or returnServerError('Error loading category ' . $this->uri);
|
||||
|
||||
foreach($html->find('ul.explore-covers-list li') as $cover) {
|
||||
foreach($covers as $cover) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = static::URI . $cover->find('a.cover-image', 0)->href;
|
||||
$item['title'] = $cover->find('.title', 0)->innertext;
|
||||
$item['uri'] = $cover->find('a.ible-title', 0)->href;
|
||||
$item['title'] = $cover->find('a.ible-title', 0)->innertext;
|
||||
$item['author'] = $this->getCategoryAuthor($cover);
|
||||
$item['content'] = '<a href='
|
||||
. $item['uri']
|
||||
. '><img src='
|
||||
. $cover->find('a.cover-image img', 0)->src
|
||||
. $cover->find('img', 0)->getAttribute('data-src')
|
||||
. '></a>';
|
||||
|
||||
$image = str_replace('.RECTANGLE1', '.LARGE', $cover->find('a.cover-image img', 0)->src);
|
||||
$item['enclosures'] = [$image];
|
||||
$item['enclosures'][] = str_replace(
|
||||
'.RECTANGLE1',
|
||||
'.LARGE',
|
||||
$cover->find('img', 0)->getAttribute('data-src')
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if(!is_null($this->getInput('category'))
|
||||
&& !is_null($this->getInput('filter'))) {
|
||||
foreach(self::PARAMETERS[$this->queriedContext]['category']['values'] as $key => $value) {
|
||||
$subcategory = array_search($this->getInput('category'), $value);
|
||||
switch($this->queriedContext) {
|
||||
case 'Category': {
|
||||
foreach(self::PARAMETERS[$this->queriedContext]['category']['values'] as $key => $value) {
|
||||
$subcategory = array_search($this->getInput('category'), $value);
|
||||
|
||||
if($subcategory !== false)
|
||||
break;
|
||||
}
|
||||
if($subcategory !== false)
|
||||
break;
|
||||
}
|
||||
|
||||
$filter = array_search(
|
||||
$this->getInput('filter'),
|
||||
self::PARAMETERS[$this->queriedContext]['filter']['values']
|
||||
);
|
||||
$filter = array_search(
|
||||
$this->getInput('filter'),
|
||||
self::PARAMETERS[$this->queriedContext]['filter']['values']
|
||||
);
|
||||
|
||||
return $subcategory . ' (' . $filter . ') - ' . static::NAME;
|
||||
return $subcategory . ' (' . $filter . ') - ' . static::NAME;
|
||||
} break;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if(!is_null($this->getInput('category'))
|
||||
&& !is_null($this->getInput('filter'))) {
|
||||
return $this->uri;
|
||||
switch($this->queriedContext) {
|
||||
case 'Category': {
|
||||
return self::URI
|
||||
. $this->getInput('category')
|
||||
. $this->getInput('filter');
|
||||
} break;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
@@ -321,24 +307,32 @@ class InstructablesBridge extends BridgeAbstract {
|
||||
* parameters list)
|
||||
*/
|
||||
private function listCategories(){
|
||||
// Use arbitrary category to receive full list
|
||||
$html = getSimpleHTMLDOM(self::URI . '/technology/');
|
||||
|
||||
foreach($html->find('.channel a') as $channel) {
|
||||
$name = html_entity_decode(trim($channel->innertext));
|
||||
// Use home page to acquire main categories
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
// Remove unwanted entities
|
||||
$name = str_replace("'", '', $name);
|
||||
$name = str_replace(''', '', $name);
|
||||
foreach($html->find('.home-content-explore-link') as $category) {
|
||||
|
||||
$uri = $channel->href;
|
||||
// Use arbitrary category to receive full list
|
||||
$html = getSimpleHTMLDOM($category->href);
|
||||
|
||||
$category = explode('/', $uri)[1];
|
||||
foreach($html->find('.channel-thumbnail a') as $channel) {
|
||||
$name = html_entity_decode(trim($channel->title));
|
||||
|
||||
if(!isset($categories)
|
||||
|| !array_key_exists($category, $categories)
|
||||
|| !in_array($uri, $categories[$category]))
|
||||
$categories[$category][$name] = $uri;
|
||||
// Remove unwanted entities
|
||||
$name = str_replace("'", '', $name);
|
||||
$name = str_replace(''', '', $name);
|
||||
|
||||
$uri = $channel->href;
|
||||
|
||||
$category_name = explode('/', $uri)[1];
|
||||
|
||||
if(!isset($categories)
|
||||
|| !array_key_exists($category_name, $categories)
|
||||
|| !in_array($uri, $categories[$category_name]))
|
||||
$categories[$category_name][$name] = $uri;
|
||||
}
|
||||
}
|
||||
|
||||
// Build PHP array manually
|
||||
@@ -360,9 +354,9 @@ class InstructablesBridge extends BridgeAbstract {
|
||||
*/
|
||||
private function getCategoryAuthor($cover) {
|
||||
return '<a href='
|
||||
. static::URI . $cover->find('span.author a', 0)->href
|
||||
. $cover->find('.ible-author a', 0)->href
|
||||
. '>'
|
||||
. $cover->find('span.author a', 0)->innertext
|
||||
. $cover->find('.ible-author a', 0)->innertext
|
||||
. '</a>';
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user