mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-18 14:22:38 +02:00
Compare commits
198 Commits
2019-03-17
...
2019-09-12
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
2cd310c025 | ||
|
b764204c3a | ||
|
a9e2574016 | ||
|
e3f6e1c6db | ||
|
8150a73922 | ||
|
a2f3866383 | ||
|
a3446ae77b | ||
|
75359bc11b | ||
|
fe103974f5 | ||
|
33c16f8be5 | ||
|
21d3bf3b60 | ||
|
3b8f3da09d | ||
|
f9c4a84c25 | ||
|
7b8dd93a8e | ||
|
8f5151b222 | ||
|
98c2530984 | ||
|
90bf90d167 | ||
|
6feda2220e | ||
|
92775abe11 | ||
|
24cdeabed8 | ||
|
380fdf2e40 | ||
|
50c90eb5df | ||
|
d9ee9e272e | ||
|
4ba0d8bebe | ||
|
c9b0cd1315 | ||
|
2dc0c36e9b | ||
|
0aa8858551 | ||
|
966d450d27 | ||
|
291e8c2a23 | ||
|
b6943de0ca | ||
|
b9bbc9bdda | ||
|
835e3b1163 | ||
|
3212156925 | ||
|
281eaacaeb | ||
|
18d5ef192c | ||
|
6293c3d33d | ||
|
835af1faf1 | ||
|
88aae6fd95 | ||
|
684558e276 | ||
|
d7094b7feb |
@@ -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.
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -236,3 +236,10 @@ config.ini.php
|
||||
|
||||
#Builder
|
||||
.buildconfig
|
||||
|
||||
#Auth
|
||||
.htaccess
|
||||
.htpasswd
|
||||
|
||||
#Crawler
|
||||
robots.txt
|
||||
|
42
.travis.yml
42
.travis.yml
@@ -4,41 +4,43 @@ language: php
|
||||
install:
|
||||
- composer global require dealerdirect/phpcodesniffer-composer-installer;
|
||||
- composer global require phpcompatibility/php-compatibility;
|
||||
# Use PHPUnit 6 for unit tests (stable), requires PHP 7
|
||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||
composer global require phpunit/phpunit ^6;
|
||||
fi
|
||||
# Use latest PHPUnit on nightly to detect breaking changes
|
||||
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
|
||||
composer global require phpunit/phpunit;
|
||||
- if [[ "$PHPUNIT" ]]; then
|
||||
composer global require phpunit/phpunit ^$PHPUNIT;
|
||||
fi
|
||||
|
||||
script:
|
||||
- phpenv rehash
|
||||
# Run PHP_CodeSniffer on all versions
|
||||
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
# Check PHP compatibility for the lowest supported version
|
||||
- if [[ $TRAVIS_PHP_VERSION == "5.6" ]]; then
|
||||
# Check PHP compatibility for the lowest and highest supported version
|
||||
- if [[ $TRAVIS_PHP_VERSION == "5.6" || $TRAVIS_PHP_VERSION == "7.3" ]]; then
|
||||
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
|
||||
fi
|
||||
# Run unit tests (stable)
|
||||
- if [[ $TRAVIS_PHP_VERSION == "7.0" ]]; then
|
||||
phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||
fi
|
||||
# Run unit tests (latest/nightly)
|
||||
# Check PHP compatibility for all versions, starting at the lowest supported version in order to detect breaking changes
|
||||
- if [[ $TRAVIS_PHP_VERSION == "nightly" ]]; then
|
||||
phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||
~/.config/composer/vendor/bin/phpcs . --standard=PHPCompatibility --extensions=php -p --runtime-set testVersion 5.6-;
|
||||
# Run unit tests on highest major version
|
||||
- if [[ ${TRAVIS_PHP_VERSION:0:1} == "7" ]]; then
|
||||
~/.config/composer/vendor/bin/phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||
fi
|
||||
|
||||
php:
|
||||
- 7.3
|
||||
|
||||
env:
|
||||
- PHPUNIT=6
|
||||
- PHPUNIT=7
|
||||
- PHPUNIT=8
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
include:
|
||||
- php: 5.6
|
||||
env: PHPUNIT=
|
||||
- php: 7.0
|
||||
- php: nightly
|
||||
- php: 7.1
|
||||
- php: 7.2
|
||||
|
||||
allow_failures:
|
||||
- php: nightly
|
||||
- php: 7.3
|
||||
env: PHPUNIT=7
|
||||
- php: 7.3
|
||||
env: PHPUNIT=8
|
||||
|
12
Dockerfile
12
Dockerfile
@@ -1,5 +1,11 @@
|
||||
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 install libxml2-dev \
|
||||
&& docker-php-ext-install -j$(nproc) simplexml \
|
||||
&& 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
|
||||
|
||||
COPY --chown=www-data:www-data ./ /app/
|
213
README.md
213
README.md
@@ -1,6 +1,6 @@
|
||||
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.
|
||||
|
||||
@@ -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
|
||||
@@ -85,7 +84,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,101 +110,117 @@ 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)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [azdkj532](https://github.com/azdkj532)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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)
|
||||
* [floviolleau](https://github.com/floviolleau)
|
||||
* [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)
|
||||
* [husim0](https://github.com/husim0)
|
||||
* [IceWreck](https://github.com/IceWreck)
|
||||
* [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)
|
||||
* [johnnygroovy](https://github.com/johnnygroovy)
|
||||
* [killruana](https://github.com/killruana)
|
||||
* [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)
|
||||
* [Leomaradan](https://github.com/Leomaradan)
|
||||
* [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)
|
||||
* [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)
|
||||
* [ThePadawan](https://github.com/ThePadawan)
|
||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||
* [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)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
|
||||
Licenses
|
||||
===
|
||||
|
@@ -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;
|
||||
|
@@ -18,20 +18,17 @@ class DisplayAction extends ActionAbstract {
|
||||
$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,10 +82,12 @@ class DisplayAction extends ActionAbstract {
|
||||
);
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setPath(PATH_CACHE);
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope('');
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setParameters($cache_params);
|
||||
$cache->setKey($cache_params);
|
||||
|
||||
$items = array();
|
||||
$infos = array();
|
||||
@@ -216,7 +215,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"]
|
||||
}
|
||||
|
121
bridges/AO3Bridge.php
Normal file
121
bridges/AO3Bridge.php
Normal file
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
class AO3Bridge extends BridgeAbstract {
|
||||
const NAME = 'AO3';
|
||||
const URI = 'https://archiveofourown.org/';
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
const DESCRIPTION = 'Returns works or chapters from Archive of Our Own';
|
||||
const MAINTAINER = 'Obsidienne';
|
||||
const PARAMETERS = array(
|
||||
'List' => array(
|
||||
'url' => array(
|
||||
'name' => 'url',
|
||||
'required' => true,
|
||||
// Example: F/F tag, complete works only
|
||||
'exampleValue' => self::URI
|
||||
. 'works?work_search[complete]=T&tag_id=F*s*F',
|
||||
),
|
||||
),
|
||||
'Bookmarks' => array(
|
||||
'user' => array(
|
||||
'name' => 'user',
|
||||
'required' => true,
|
||||
// Example: Nyaaru's bookmarks
|
||||
'exampleValue' => 'Nyaaru',
|
||||
),
|
||||
),
|
||||
'Work' => array(
|
||||
'id' => array(
|
||||
'name' => 'id',
|
||||
'required' => true,
|
||||
// Example: latest chapters from A Better Past by LysSerris
|
||||
'exampleValue' => '18181853',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Feed for lists of works (e.g. recent works, search results, filtered tags,
|
||||
// bookmarks, series, collections).
|
||||
private function collectList($url) {
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('could not request AO3');
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('.index.group > li') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('div h4 a', 0);
|
||||
if (!isset($title)) continue; // discard deleted works
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['content'] = $element;
|
||||
$item['uri'] = $title->href;
|
||||
|
||||
$strdate = $element->find('div p.datetime', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($strdate);
|
||||
|
||||
$chapters = $element->find('dl dd.chapters', 0);
|
||||
// bookmarked series and external works do not have a chapters count
|
||||
$chapters = (isset($chapters) ? $chapters->plaintext : 0);
|
||||
$item['uid'] = $item['uri'] . "/$strdate/$chapters";
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// Feed for recent chapters of a specific work.
|
||||
private function collectWork($id) {
|
||||
$url = self::URI . "/works/$id/navigate";
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('could not request AO3');
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$this->title = $html->find('h2 a', 0)->plaintext;
|
||||
|
||||
foreach($html->find('ol.index.group > li') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $element->find('a', 0)->plaintext;
|
||||
$item['content'] = $element;
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
|
||||
$strdate = $element->find('span.datetime', 0)->plaintext;
|
||||
$strdate = str_replace('(', '', $strdate);
|
||||
$strdate = str_replace(')', '', $strdate);
|
||||
$item['timestamp'] = strtotime($strdate);
|
||||
|
||||
$item['uid'] = $item['uri'] . "/$strdate";
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
$this->items = array_reverse($this->items);
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Bookmarks':
|
||||
$user = $this->getInput('user');
|
||||
$this->title = $user;
|
||||
$url = self::URI
|
||||
. '/users/' . $user
|
||||
. '/bookmarks?bookmark_search[sort_column]=bookmarkable_date';
|
||||
return $this->collectList($url);
|
||||
case 'List': return $this->collectList(
|
||||
$this->getInput('url')
|
||||
);
|
||||
case 'Work': return $this->collectWork(
|
||||
$this->getInput('id')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
$name = parent::getName() . " $this->queriedContext";
|
||||
if (isset($this->title)) $name .= " - $this->title";
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/favicon.ico';
|
||||
}
|
||||
}
|
93
bridges/ArtStationBridge.php
Normal file
93
bridges/ArtStationBridge.php
Normal file
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
class ArtStationBridge extends BridgeAbstract {
|
||||
const NAME = 'ArtStation';
|
||||
const URI = 'https://www.artstation.com';
|
||||
const DESCRIPTION = 'Fetches the latest ten artworks from a search query on ArtStation.';
|
||||
const MAINTAINER = 'thefranke';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Search Query' => array(
|
||||
'q' => array(
|
||||
'name' => 'Search term',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.artstation.com/assets/favicon-58653022bc38c1905ac7aa1b10bffa6b.ico';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return self::NAME . ': ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
private function fetchSearch($searchQuery) {
|
||||
$data = '{"query":"' . $searchQuery . '","page":1,"per_page":50,"sorting":"date",';
|
||||
$data .= '"pro_first":"1","filters":[],"additional_fields":[]}';
|
||||
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json'
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
);
|
||||
|
||||
$jsonSearchURL = self::URI . '/api/v2/search/projects.json';
|
||||
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts)
|
||||
or returnServerError('Could not fetch JSON for search query.');
|
||||
return json_decode($jsonSearchStr);
|
||||
}
|
||||
|
||||
private function fetchProject($hashID) {
|
||||
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
|
||||
$jsonProjectStr = getContents($jsonProjectURL)
|
||||
or returnServerError('Could not fetch JSON for project.');
|
||||
return json_decode($jsonProjectStr);
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$searchTerm = $this->getInput('q');
|
||||
$jsonQuery = $this->fetchSearch($searchTerm);
|
||||
|
||||
foreach($jsonQuery->data as $media) {
|
||||
// get detailed info about media item
|
||||
$jsonProject = $this->fetchProject($media->hash_id);
|
||||
|
||||
// create item
|
||||
$item = array();
|
||||
$item['title'] = $media->title;
|
||||
$item['uri'] = $media->url;
|
||||
$item['timestamp'] = strtotime($jsonProject->published_at);
|
||||
$item['author'] = $media->user->full_name;
|
||||
$item['categories'] = implode(',', $jsonProject->tags);
|
||||
|
||||
$item['content'] = '<a href="'
|
||||
. $media->url
|
||||
. '"><img style="max-width: 100%" src="'
|
||||
. $jsonProject->cover_url
|
||||
. '"></a><p>'
|
||||
. $jsonProject->description
|
||||
. '</p>';
|
||||
|
||||
$numAssets = count($jsonProject->assets);
|
||||
|
||||
if ($numAssets > 1)
|
||||
$item['content'] .= '<p><a href="'
|
||||
. $media->url
|
||||
. '">Project contains '
|
||||
. ($numAssets - 1)
|
||||
. ' more item(s).</a></p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
@@ -3,63 +3,184 @@
|
||||
class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'AutoJM';
|
||||
const URI = 'http://www.autojm.fr/';
|
||||
const URI = 'https://www.autojm.fr/';
|
||||
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM' => array(
|
||||
'url' => array(
|
||||
'name' => 'URL de la recherche',
|
||||
'name' => 'URL du modèle',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
|
||||
'exampleValue' => 'gammes/index/398?order_by=finition_asc&energie[]=3&transmission[]=2&dispo=all'
|
||||
'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p'
|
||||
),
|
||||
'energy' => array(
|
||||
'name' => 'Carburant',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'-' => '',
|
||||
'Diesel' => 1,
|
||||
'Essence' => 3,
|
||||
'Hybride' => 5
|
||||
),
|
||||
'title' => 'Carburant'
|
||||
),
|
||||
'transmission' => array(
|
||||
'name' => 'Transmission',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'-' => '',
|
||||
'Automatique' => 1,
|
||||
'Manuelle' => 2
|
||||
),
|
||||
'title' => 'Transmission'
|
||||
),
|
||||
'priceMin' => array(
|
||||
'name' => 'Prix minimum',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Prix minimum du véhicule',
|
||||
'exampleValue' => '10000',
|
||||
'defaultValue' => '0'
|
||||
),
|
||||
'priceMax' => array(
|
||||
'name' => 'Prix maximum',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Prix maximum du véhicule',
|
||||
'exampleValue' => '15000',
|
||||
'defaultValue' => '150000'
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/images/favicon.ico';
|
||||
return self::URI . 'favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
or returnServerError('Could not request AutoJM.');
|
||||
$list = $html->find('div[class*=ligne_modele]');
|
||||
foreach($list as $element) {
|
||||
$image = $element->find('img[class=width-100]', 0)->src;
|
||||
$serie = $element->find('div[class=serie]', 0)->find('span', 0)->plaintext;
|
||||
$url = $element->find('div[class=serie]', 0)->find('a[class=btn_ligne color-black]', 0)->href;
|
||||
if($element->find('div[class*=hasStock-info]', 0) != null) {
|
||||
$dispo = 'Disponible';
|
||||
} else {
|
||||
$dispo = 'Sur commande';
|
||||
}
|
||||
$carburant = str_replace('dispo |', '', $element->find('div[class=carburant]', 0)->plaintext);
|
||||
$transmission = $element->find('div[class*=bv]', 0)->plaintext;
|
||||
$places = $element->find('div[class*=places]', 0)->plaintext;
|
||||
$portes = $element->find('div[class*=nb_portes]', 0)->plaintext;
|
||||
$carosserie = $element->find('div[class*=coloris]', 0)->plaintext;
|
||||
$remise = $element->find('div[class*=remise]', 0)->plaintext;
|
||||
$prix = $element->find('div[class*=prixjm]', 0)->plaintext;
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = $serie;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />' . $serie . '</p>';
|
||||
$item['content'] .= '<ul><li>Disponibilité : ' . $dispo . '</li>';
|
||||
$item['content'] .= '<li>Carburant : ' . $carburant . '</li>';
|
||||
$item['content'] .= '<li>Transmission : ' . $transmission . '</li>';
|
||||
$item['content'] .= '<li>Nombre de places : ' . $places . '</li>';
|
||||
$item['content'] .= '<li>Nombre de portes : ' . $portes . '</li>';
|
||||
$item['content'] .= '<li>Série : ' . $serie . '</li>';
|
||||
$item['content'] .= '<li>Carosserie : ' . $carosserie . '</li>';
|
||||
$item['content'] .= '<li>Remise : ' . $remise . '</li>';
|
||||
$item['content'] .= '<li>Prix : ' . $prix . '</li></ul>';
|
||||
|
||||
$this->items[] = $item;
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Afficher les offres de véhicules disponible en fonction des critères du site AutoJM':
|
||||
$html = getSimpleHTMLDOMCached(self::URI . $this->getInput('url'), 86400);
|
||||
$name = html_entity_decode($html->find('title', 0)->plaintext);
|
||||
return $name;
|
||||
break;
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$model_url = self::URI . $this->getInput('url');
|
||||
|
||||
// Get the session cookies and the form token
|
||||
$this->getInitialParameters($model_url);
|
||||
|
||||
// Build the form
|
||||
$post_data = array(
|
||||
'form[energy]' => $this->getInput('energy'),
|
||||
'form[transmission]' => $this->getInput('transmission'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[_token]' => $this->token
|
||||
);
|
||||
|
||||
// Set the Form request content type
|
||||
$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
|
||||
);
|
||||
|
||||
// Get the JSON content of the form
|
||||
$json = getContents($model_url, $header, $curl_opts)
|
||||
or returnServerError('Could not request AutoJM.');
|
||||
|
||||
// Extract the HTML content from the JSON result
|
||||
$data = json_decode($json);
|
||||
$html = str_get_html($data->content);
|
||||
|
||||
// Go through every finisha of the model
|
||||
$list = $html->find('h3');
|
||||
foreach ($list as $finish) {
|
||||
$finish_name = $finish->plaintext;
|
||||
$motorizations = $finish->next_sibling()->find('li');
|
||||
foreach ($motorizations as $element) {
|
||||
$image = $element->find('div[class=block-product-image]', 0)->{'data-ga-banner'};
|
||||
$serie = $element->find('span[class=model]', 0)->plaintext;
|
||||
$url = self::URI . substr($element->find('a', 0)->href, 1);
|
||||
if ($element->find('span[class*=block-product-nbModel]', 0) != null) {
|
||||
$availability = 'En Stock';
|
||||
} else {
|
||||
$availability = 'Sur commande';
|
||||
}
|
||||
$discount_html = $element->find('span[class*=tag--promo]', 0);
|
||||
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>';
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
$this->cookies = trim(substr($cookies, 1));
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
@@ -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'));
|
||||
|
435
bridges/BadDragonBridge.php
Normal file
435
bridges/BadDragonBridge.php
Normal file
@@ -0,0 +1,435 @@
|
||||
<?php
|
||||
class BadDragonBridge extends BridgeAbstract {
|
||||
const NAME = 'Bad Dragon Bridge';
|
||||
const URI = 'https://bad-dragon.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Returns sales or new clearance items';
|
||||
const MAINTAINER = 'Roliga';
|
||||
const PARAMETERS = array(
|
||||
'Sales' => array(
|
||||
),
|
||||
'Clearance' => array(
|
||||
'ready_made' => array(
|
||||
'name' => 'Ready Made',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'flop' => array(
|
||||
'name' => 'Flops',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'skus' => array(
|
||||
'name' => 'Products',
|
||||
'exampleValue' => 'chanceflared, crackers',
|
||||
'title' => 'Comma separated list of product SKUs'
|
||||
),
|
||||
'onesize' => array(
|
||||
'name' => 'One-Size',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'mini' => array(
|
||||
'name' => 'Mini',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'small' => array(
|
||||
'name' => 'Small',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'medium' => array(
|
||||
'name' => 'Medium',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'large' => array(
|
||||
'name' => 'Large',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'extralarge' => array(
|
||||
'name' => 'Extra Large',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Accessories' => 'accessories',
|
||||
'Merchandise' => 'merchandise',
|
||||
'Dildos' => 'insertable',
|
||||
'Masturbators' => 'penetrable',
|
||||
'Packers' => 'packer',
|
||||
'Lil\' Squirts' => 'shooter',
|
||||
'Lil\' Vibes' => 'vibrator',
|
||||
'Wearables' => 'wearable'
|
||||
),
|
||||
'defaultValue' => 'all',
|
||||
),
|
||||
'soft' => array(
|
||||
'name' => 'Soft Firmness',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'med_firm' => array(
|
||||
'name' => 'Medium Firmness',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'firm' => array(
|
||||
'name' => 'Firm',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'split' => array(
|
||||
'name' => 'Split Firmness',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'maxprice' => array(
|
||||
'name' => 'Max Price',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 300
|
||||
),
|
||||
'minprice' => array(
|
||||
'name' => 'Min Price',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 0
|
||||
),
|
||||
'cumtube' => array(
|
||||
'name' => 'Cumtube',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'suctionCup' => array(
|
||||
'name' => 'Suction Cup',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'noAccessories' => array(
|
||||
'name' => 'No Accessories',
|
||||
'type' => 'checkbox'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* This sets index $strFrom (or $strTo if set) in $outArr to 'on' if
|
||||
* $inArr[$param] contains $strFrom.
|
||||
* It is used for translating BD's shop filter URLs into something we can use.
|
||||
*
|
||||
* For the query '?type[]=ready_made&type[]=flop' we would have an array like:
|
||||
* Array (
|
||||
* [type] => Array (
|
||||
* [0] => ready_made
|
||||
* [1] => flop
|
||||
* )
|
||||
* )
|
||||
* which could be translated into:
|
||||
* Array (
|
||||
* [ready_made] => on
|
||||
* [flop] => on
|
||||
* )
|
||||
* */
|
||||
private function setParam($inArr, &$outArr, $param, $strFrom, $strTo = null) {
|
||||
if(isset($inArr[$param]) && in_array($strFrom, $inArr[$param])) {
|
||||
$outArr[($strTo ?: $strFrom)] = 'on';
|
||||
}
|
||||
}
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// Sale
|
||||
$regex = '/^(https?:\/\/)?bad-dragon\.com\/sales/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Clearance
|
||||
$regex = '/^(https?:\/\/)?bad-dragon\.com\/shop\/clearance/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
parse_str(parse_url($url, PHP_URL_QUERY), $urlParams);
|
||||
|
||||
$this->setParam($urlParams, $params, 'type', 'ready_made');
|
||||
$this->setParam($urlParams, $params, 'type', 'flop');
|
||||
|
||||
if(isset($urlParams['skus'])) {
|
||||
$skus = array();
|
||||
foreach($urlParams['skus'] as $sku) {
|
||||
is_string($sku) && $skus[] = $sku;
|
||||
is_array($sku) && $skus[] = $sku[0];
|
||||
}
|
||||
$params['skus'] = implode(',', $skus);
|
||||
}
|
||||
|
||||
$this->setParam($urlParams, $params, 'sizes', 'onesize');
|
||||
$this->setParam($urlParams, $params, 'sizes', 'mini');
|
||||
$this->setParam($urlParams, $params, 'sizes', 'small');
|
||||
$this->setParam($urlParams, $params, 'sizes', 'medium');
|
||||
$this->setParam($urlParams, $params, 'sizes', 'large');
|
||||
$this->setParam($urlParams, $params, 'sizes', 'extralarge');
|
||||
|
||||
if(isset($urlParams['category'])) {
|
||||
$params['category'] = strtolower($urlParams['category']);
|
||||
} else{
|
||||
$params['category'] = 'all';
|
||||
}
|
||||
|
||||
$this->setParam($urlParams, $params, 'firmnessValues', 'soft');
|
||||
$this->setParam($urlParams, $params, 'firmnessValues', 'medium', 'med_firm');
|
||||
$this->setParam($urlParams, $params, 'firmnessValues', 'firm');
|
||||
$this->setParam($urlParams, $params, 'firmnessValues', 'split');
|
||||
|
||||
if(isset($urlParams['price'])) {
|
||||
isset($urlParams['price']['max'])
|
||||
&& $params['maxprice'] = $urlParams['price']['max'];
|
||||
isset($urlParams['price']['min'])
|
||||
&& $params['minprice'] = $urlParams['price']['min'];
|
||||
}
|
||||
|
||||
isset($urlParams['cumtube'])
|
||||
&& $urlParams['cumtube'] === '1'
|
||||
&& $params['cumtube'] = 'on';
|
||||
isset($urlParams['suctionCup'])
|
||||
&& $urlParams['suctionCup'] === '1'
|
||||
&& $params['suctionCup'] = 'on';
|
||||
isset($urlParams['noAccessories'])
|
||||
&& $urlParams['noAccessories'] === '1'
|
||||
&& $params['noAccessories'] = 'on';
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Sales':
|
||||
return 'Bad Dragon Sales';
|
||||
case 'Clearance':
|
||||
return 'Bad Dragon Clearance Search';
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Sales':
|
||||
return self::URI . 'sales';
|
||||
case 'Clearance':
|
||||
return $this->inputToURL();
|
||||
default:
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Sales':
|
||||
$sales = json_decode(getContents(self::URI . 'api/sales'))
|
||||
or returnServerError('Failed to query BD API');
|
||||
|
||||
foreach($sales as $sale) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $sale->title;
|
||||
$item['timestamp'] = strtotime($sale->startDate);
|
||||
|
||||
$item['uri'] = $this->getURI() . '/' . $sale->slug;
|
||||
|
||||
$contentHTML = '<p><img src="' . $sale->image->url . '"></p>';
|
||||
if(isset($sale->endDate)) {
|
||||
$contentHTML .= '<p><b>This promotion ends on '
|
||||
. gmdate('M j, Y \a\t g:i A T', strtotime($sale->endDate))
|
||||
. '</b></p>';
|
||||
} else {
|
||||
$contentHTML .= '<p><b>This promotion never ends</b></p>';
|
||||
}
|
||||
$ul = false;
|
||||
$content = json_decode($sale->content);
|
||||
foreach($content->blocks as $block) {
|
||||
switch($block->type) {
|
||||
case 'header-one':
|
||||
$contentHTML .= '<h1>' . $block->text . '</h1>';
|
||||
break;
|
||||
case 'header-two':
|
||||
$contentHTML .= '<h2>' . $block->text . '</h2>';
|
||||
break;
|
||||
case 'header-three':
|
||||
$contentHTML .= '<h3>' . $block->text . '</h3>';
|
||||
break;
|
||||
case 'unordered-list-item':
|
||||
if(!$ul) {
|
||||
$contentHTML .= '<ul>';
|
||||
$ul = true;
|
||||
}
|
||||
$contentHTML .= '<li>' . $block->text . '</li>';
|
||||
break;
|
||||
default:
|
||||
if($ul) {
|
||||
$contentHTML .= '</ul>';
|
||||
$ul = false;
|
||||
}
|
||||
$contentHTML .= '<p>' . $block->text . '</p>';
|
||||
break;
|
||||
}
|
||||
}
|
||||
$item['content'] = $contentHTML;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
case 'Clearance':
|
||||
$toyData = json_decode(getContents($this->inputToURL(true)))
|
||||
or returnServerError('Failed to query BD API');
|
||||
|
||||
$productList = json_decode(getContents(self::URI
|
||||
. 'api/inventory-toy/product-list'))
|
||||
or returnServerError('Failed to query BD API');
|
||||
|
||||
foreach($toyData->toys as $toy) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI()
|
||||
. '#'
|
||||
. $toy->id;
|
||||
$item['timestamp'] = strtotime($toy->created);
|
||||
|
||||
foreach($productList as $product) {
|
||||
if($product->sku == $toy->sku) {
|
||||
$item['title'] = $product->name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// images
|
||||
$content = '<p>';
|
||||
foreach($toy->images as $image) {
|
||||
$content .= '<a href="'
|
||||
. $image->fullFilename
|
||||
. '"><img src="'
|
||||
. $image->thumbFilename
|
||||
. '" /></a>';
|
||||
}
|
||||
// price
|
||||
$content .= '</p><p><b>Price:</b> $'
|
||||
. $toy->price
|
||||
// size
|
||||
. '<br /><b>Size:</b> '
|
||||
. $toy->size
|
||||
// color
|
||||
. '<br /><b>Color:</b> '
|
||||
. $toy->color
|
||||
// features
|
||||
. '<br /><b>Features:</b> '
|
||||
. ($toy->suction_cup ? 'Suction cup' : '')
|
||||
. ($toy->suction_cup && $toy->cumtube ? ', ' : '')
|
||||
. ($toy->cumtube ? 'Cumtube' : '')
|
||||
. ($toy->suction_cup || $toy->cumtube ? '' : 'None');
|
||||
// firmness
|
||||
$firmnessTexts = array(
|
||||
'2' => 'Extra soft',
|
||||
'3' => 'Soft',
|
||||
'5' => 'Medium',
|
||||
'8' => 'Firm'
|
||||
);
|
||||
$firmnesses = explode('/', $toy->firmness);
|
||||
if(count($firmnesses) === 2) {
|
||||
$content .= '<br /><b>Firmness:</b> '
|
||||
. $firmnessTexts[$firmnesses[0]]
|
||||
. ', '
|
||||
. $firmnessTexts[$firmnesses[1]];
|
||||
} else{
|
||||
$content .= '<br /><b>Firmness:</b> '
|
||||
. $firmnessTexts[$firmnesses[0]];
|
||||
}
|
||||
// flop
|
||||
if($toy->type === 'flop') {
|
||||
$content .= '<br /><b>Flop reason:</b> '
|
||||
. $toy->flop_reason;
|
||||
}
|
||||
$content .= '</p>';
|
||||
$item['content'] = $content;
|
||||
|
||||
$enclosures = array();
|
||||
foreach($toy->images as $image) {
|
||||
$enclosures[] = $image->fullFilename;
|
||||
}
|
||||
$item['enclosures'] = $enclosures;
|
||||
|
||||
$categories = array();
|
||||
$categories[] = $toy->sku;
|
||||
$categories[] = $toy->type;
|
||||
$categories[] = $toy->size;
|
||||
if($toy->cumtube) {
|
||||
$categories[] = 'cumtube';
|
||||
}
|
||||
if($toy->suction_cup) {
|
||||
$categories[] = 'suction_cup';
|
||||
}
|
||||
$item['categories'] = $categories;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function inputToURL($api = false) {
|
||||
$url = self::URI;
|
||||
$url .= ($api ? 'api/inventory-toys?' : 'shop/clearance?');
|
||||
|
||||
// Default parameters
|
||||
$url .= 'limit=60';
|
||||
$url .= '&page=1';
|
||||
$url .= '&sort[field]=created';
|
||||
$url .= '&sort[direction]=desc';
|
||||
|
||||
// Product types
|
||||
$url .= ($this->getInput('ready_made') ? '&type[]=ready_made' : '');
|
||||
$url .= ($this->getInput('flop') ? '&type[]=flop' : '');
|
||||
|
||||
// Product names
|
||||
foreach(array_filter(explode(',', $this->getInput('skus'))) as $sku) {
|
||||
$url .= '&skus[]=' . urlencode(trim($sku));
|
||||
}
|
||||
|
||||
// Size
|
||||
$url .= ($this->getInput('onesize') ? '&sizes[]=onesize' : '');
|
||||
$url .= ($this->getInput('mini') ? '&sizes[]=mini' : '');
|
||||
$url .= ($this->getInput('small') ? '&sizes[]=small' : '');
|
||||
$url .= ($this->getInput('medium') ? '&sizes[]=medium' : '');
|
||||
$url .= ($this->getInput('large') ? '&sizes[]=large' : '');
|
||||
$url .= ($this->getInput('extralarge') ? '&sizes[]=extralarge' : '');
|
||||
|
||||
// Category
|
||||
$url .= ($this->getInput('category') ? '&category='
|
||||
. urlencode($this->getInput('category')) : '');
|
||||
|
||||
// Firmness
|
||||
if($api) {
|
||||
$url .= ($this->getInput('soft') ? '&firmnessValues[]=3' : '');
|
||||
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=5' : '');
|
||||
$url .= ($this->getInput('firm') ? '&firmnessValues[]=8' : '');
|
||||
if($this->getInput('split')) {
|
||||
$url .= '&firmnessValues[]=3/5';
|
||||
$url .= '&firmnessValues[]=3/8';
|
||||
$url .= '&firmnessValues[]=8/3';
|
||||
$url .= '&firmnessValues[]=5/8';
|
||||
$url .= '&firmnessValues[]=8/5';
|
||||
}
|
||||
} else{
|
||||
$url .= ($this->getInput('soft') ? '&firmnessValues[]=soft' : '');
|
||||
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=medium' : '');
|
||||
$url .= ($this->getInput('firm') ? '&firmnessValues[]=firm' : '');
|
||||
$url .= ($this->getInput('split') ? '&firmnessValues[]=split' : '');
|
||||
}
|
||||
|
||||
// Price
|
||||
$url .= ($this->getInput('maxprice') ? '&price[max]='
|
||||
. $this->getInput('maxprice') : '&price[max]=300');
|
||||
$url .= ($this->getInput('minprice') ? '&price[min]='
|
||||
. $this->getInput('minprice') : '&price[min]=0');
|
||||
|
||||
// Features
|
||||
$url .= ($this->getInput('cumtube') ? '&cumtube=1' : '');
|
||||
$url .= ($this->getInput('suctionCup') ? '&suctionCup=1' : '');
|
||||
$url .= ($this->getInput('noAccessories') ? '&noAccessories=1' : '');
|
||||
|
||||
return $url;
|
||||
}
|
||||
}
|
@@ -68,7 +68,7 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
|
||||
|
||||
$item['title'] = implode(' ', $title);
|
||||
$item['uri'] = $this->getURI();
|
||||
$item['uid'] = hash('sha1', $item['title']);
|
||||
$item['uid'] = $this->getSanitizedHash($item['title']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
@@ -89,8 +89,12 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getSanitizedHash($string) {
|
||||
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
|
||||
}
|
||||
|
||||
private function filterText($text) {
|
||||
return rtrim($text, '*');
|
||||
return rtrim($text, '* ');
|
||||
}
|
||||
|
||||
private function filterHTML($text) {
|
||||
|
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();
|
||||
}
|
||||
}
|
119
bridges/BingSearchBridge.php
Normal file
119
bridges/BingSearchBridge.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
class BingSearchBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bing search';
|
||||
const URI = 'https://www.bing.com/';
|
||||
const DESCRIPTION = 'Return images from bing search discover';
|
||||
const MAINTAINER = 'DnAp';
|
||||
const PARAMETERS = array(
|
||||
'Image Discover' => array(
|
||||
'category' => array(
|
||||
'name' => 'Categories',
|
||||
'type' => 'list',
|
||||
'values' => self::IMAGE_DISCOVER_CATEGORIES
|
||||
),
|
||||
'image_size' => array(
|
||||
'name' => 'Image size',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Small' => 'turl',
|
||||
'Full size' => 'imgurl'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const IMAGE_DISCOVER_CATEGORIES = array(
|
||||
'Abstract' => 'abstract',
|
||||
'Animals' => 'animals',
|
||||
'Anime' => 'anime',
|
||||
'Architecture' => 'architecture',
|
||||
'Arts and Crafts' => 'arts-and-crafts',
|
||||
'Beauty' => 'beauty',
|
||||
'Cars and Motorcycles' => 'cars-and-motorcycles',
|
||||
'Cats' => 'cats',
|
||||
'Celebrities' => 'celebrities',
|
||||
'Comics' => 'comics',
|
||||
'DIY' => 'diy',
|
||||
'Dogs' => 'dogs',
|
||||
'Fitness' => 'fitness',
|
||||
'Food and Drink' => 'food-and-drink',
|
||||
'Funny' => 'funny',
|
||||
'Gadgets' => 'gadgets',
|
||||
'Gardening' => 'gardening',
|
||||
'Geeky' => 'geeky',
|
||||
'Hairstyles' => 'hairstyles',
|
||||
'Home Decor' => 'home-decor',
|
||||
'Marine Life' => 'marine-life',
|
||||
'Men\'s Fashion' => 'men%27s-fashion',
|
||||
'Nature' => 'nature',
|
||||
'Outdoors' => 'outdoors',
|
||||
'Parenting' => 'parenting',
|
||||
'Phone Wallpapers' => 'phone-wallpapers',
|
||||
'Photography' => 'photography',
|
||||
'Quotes' => 'quotes',
|
||||
'Recipes' => 'recipes',
|
||||
'Snow' => 'snow',
|
||||
'Tattoos' => 'tattoos',
|
||||
'Travel' => 'travel',
|
||||
'Video Games' => 'video-games',
|
||||
'Weddings' => 'weddings',
|
||||
'Women\'s Fashion' => 'women%27s-fashion',
|
||||
);
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.bing.com/sa/simg/bing_p_rr_teal_min.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$this->items = $this->imageDiscover($this->getInput('category'));
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if ($this->getInput('category')) {
|
||||
if (self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')] !== null) {
|
||||
$category = self::IMAGE_DISCOVER_CATEGORIES[$this->getInput('categories')];
|
||||
} else {
|
||||
$category = 'Unknown';
|
||||
}
|
||||
|
||||
return 'Best ' . $category . ' - Bing Image Discover';
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function imageDiscover($category)
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI . '/discover/' . $category)
|
||||
or returnServerError('Could not request ' . self::NAME);
|
||||
$sizeKey = $this->getInput('image_size');
|
||||
|
||||
$items = [];
|
||||
foreach ($html->find('a.iusc') as $element) {
|
||||
$data = json_decode(htmlspecialchars_decode($element->getAttribute('m')), true);
|
||||
|
||||
$item = array();
|
||||
$item['title'] = basename(rtrim($data['imgurl'], '/'));
|
||||
$item['uri'] = $data['imgurl'];
|
||||
$item['content'] = '<a href="' . $data['imgurl'] . '">
|
||||
<img src="' . $data[$sizeKey] . '" alt="' . $item['title'] . '"></a>
|
||||
<p>Source: <a href="' . $this->curUrl($data['surl']) . '"> </a></p>';
|
||||
$item['enclosures'] = $data['imgurl'];
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
|
||||
private function curUrl($url)
|
||||
{
|
||||
if (strlen($url) <= 80) {
|
||||
return $url;
|
||||
}
|
||||
return substr($url, 0, 80) . '...';
|
||||
}
|
||||
}
|
157
bridges/BrutBridge.php
Normal file
157
bridges/BrutBridge.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?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',
|
||||
),
|
||||
'defaultValue' => 'news',
|
||||
),
|
||||
'edition' => array(
|
||||
'name' => ' Edition',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'United States' => 'us',
|
||||
'United Kingdom' => 'uk',
|
||||
'France' => 'fr',
|
||||
'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 = [];
|
||||
private $bannedURL = [];
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
@@ -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!');
|
||||
|
@@ -14,7 +14,7 @@ class ComboiosDePortugalBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $element->innertext;
|
||||
$item['uri'] = self::BASE_URI . $element->href;
|
||||
$item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href)));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class CourrierInternationalBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Courrier International Bridge';
|
||||
const URI = 'http://CourrierInternational.com/';
|
||||
const URI = 'https://www.courrierinternational.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5 min
|
||||
const DESCRIPTION = 'Courrier International bridge';
|
||||
|
||||
|
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;
|
||||
|
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++;
|
||||
}
|
||||
}
|
||||
}
|
@@ -159,13 +159,13 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
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;
|
||||
|
@@ -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';
|
||||
}
|
63
bridges/EconomistBridge.php
Normal file
63
bridges/EconomistBridge.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
class EconomistBridge extends BridgeAbstract {
|
||||
const NAME = 'The Economist: Latest Updates';
|
||||
const URI = 'https://www.economist.com';
|
||||
const DESCRIPTION = 'Fetches the latest updates from the Economist.';
|
||||
const MAINTAINER = 'thefranke';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.economist.com/sites/default/files/econfinal_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI . '/latest/')
|
||||
or returnServerError('Could not fetch latest updates form The Economist.');
|
||||
|
||||
foreach($html->find('article') as $element) {
|
||||
|
||||
$a = $element->find('a', 0);
|
||||
$href = self::URI . $a->href;
|
||||
$full = getSimpleHTMLDOMCached($href);
|
||||
$article = $full->find('article', 0);
|
||||
|
||||
$header = $article->find('h1', 0);
|
||||
$author = $article->find('span[itemprop="author"]', 0);
|
||||
$time = $article->find('time[itemprop="dateCreated"]', 0);
|
||||
$content = $article->find('div[itemprop="description"]', 0);
|
||||
|
||||
// Remove newsletter subscription box
|
||||
$newsletter = $content->find('div[class="newsletter-form__message"]', 0);
|
||||
if ($newsletter)
|
||||
$newsletter->outertext = '';
|
||||
|
||||
$newsletterForm = $content->find('form', 0);
|
||||
if ($newsletterForm)
|
||||
$newsletterForm->outertext = '';
|
||||
|
||||
// Remove next and previous article URLs at the bottom
|
||||
$nextprev = $content->find('div[class="blog-post__next-previous-wrapper"]', 0);
|
||||
if ($nextprev)
|
||||
$nextprev->outertext = '';
|
||||
|
||||
$section = [ $article->find('h3[itemprop="articleSection"]', 0)->plaintext ];
|
||||
|
||||
$item = array();
|
||||
$item['title'] = $header->find('span', 0)->innertext . ': '
|
||||
. $header->find('span', 1)->innertext;
|
||||
|
||||
$item['uri'] = $href;
|
||||
$item['timestamp'] = strtotime($time->datetime);
|
||||
$item['author'] = $author->innertext;
|
||||
$item['categories'] = $section;
|
||||
|
||||
$item['content'] = '<img style="max-width: 100%" src="'
|
||||
. $a->find('img', 0)->src . '">' . $content->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
|
@@ -120,9 +120,11 @@ class ElloBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function getAPIKey() {
|
||||
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setPath(PATH_CACHE);
|
||||
$cache->setParameters(['key']);
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope(get_called_class());
|
||||
$cache->setKey(['key']);
|
||||
$key = $cache->loadData();
|
||||
|
||||
if($key == null) {
|
||||
|
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;
|
||||
}
|
||||
}
|
@@ -72,15 +72,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,7 +95,7 @@ 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];
|
||||
|
@@ -28,14 +28,14 @@ class FDroidBridge extends BridgeAbstract {
|
||||
or returnServerError('Could not request F-Droid.');
|
||||
|
||||
// targetting the corresponding widget based on user selection
|
||||
// "updated" is the 4th widget on the page, "added" is the 5th
|
||||
// "updated" is the 5th widget on the page, "added" is the 6th
|
||||
|
||||
switch($this->getInput('u')) {
|
||||
case 'updated':
|
||||
$html_widget = $html->find('div.sidebar-widget', 4);
|
||||
$html_widget = $html->find('div.sidebar-widget', 5);
|
||||
break;
|
||||
default:
|
||||
$html_widget = $html->find('div.sidebar-widget', 5);
|
||||
$html_widget = $html->find('div.sidebar-widget', 6);
|
||||
break;
|
||||
}
|
||||
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -142,7 +142,11 @@ 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)
|
||||
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
||||
@@ -219,8 +223,7 @@ class FacebookBridge extends BridgeAbstract {
|
||||
$ogtitle = $html->find('meta[property="og:title"]', 0)
|
||||
or returnServerError('Unable to find group title!');
|
||||
|
||||
return htmlspecialchars_decode($ogtitle->content, ENT_QUOTES);
|
||||
|
||||
return html_entity_decode($ogtitle->content, ENT_QUOTES);
|
||||
}
|
||||
|
||||
private function extractGroupURI($post) {
|
||||
@@ -506,7 +509,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.');
|
||||
@@ -581,6 +588,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) {
|
||||
|
164
bridges/FicbookBridge.php
Normal file
164
bridges/FicbookBridge.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?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',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
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 collectData() {
|
||||
|
||||
$header = array('Accept-Language: en-US');
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
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.post') 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.table-of-contents > 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('.css_LstPic img', 0)->getAttribute('src');
|
||||
$item['title'] = $annonce->find('.css_LstCtrls span', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $annonce->find('.css_PnlCtrls a', 0)->href;
|
||||
$content = $annonce->find('.css_LstDtl div', 2)->innertext;
|
||||
$item['content'] = "<img src='$img' /><br>$content";
|
||||
$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'] = $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;
|
||||
|
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';
|
||||
}
|
||||
}
|
||||
}
|
@@ -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=ArticleBodyContent]', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
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,10 +66,21 @@ 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;
|
||||
}
|
||||
|
||||
private function extractIssueEvent($issueNbr, $title, $comment){
|
||||
|
||||
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
|
||||
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
@@ -94,22 +105,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,25 +128,32 @@ 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) {
|
||||
|
||||
$comments = $issue->find('
|
||||
[id^="issue-"] > .comment,
|
||||
[id^="issuecomment-"] > .comment,
|
||||
[id^="event-"],
|
||||
[id^="ref-"]
|
||||
');
|
||||
foreach($comments as $comment) {
|
||||
|
||||
if (!$comment->hasChildNodes()) {
|
||||
continue;
|
||||
}
|
||||
$comment = $comment->firstChild();
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
if (in_array('timeline-comment-wrapper', $classes)) {
|
||||
|
||||
if (!$comment->hasClass('discussion-item-header')) {
|
||||
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
continue;
|
||||
}
|
||||
while (in_array('discussion-item', $classes)) {
|
||||
|
||||
while ($comment->hasClass('discussion-item-header')) {
|
||||
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
$comment = $comment->nextSibling();
|
||||
@@ -145,6 +162,7 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
}
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
}
|
||||
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
@@ -192,8 +210,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 +239,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,
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,7 @@ class GithubSearchBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
foreach($html->find('div.repo-list-item') as $element) {
|
||||
foreach($html->find('li.repo-list-item') as $element) {
|
||||
$item = array();
|
||||
|
||||
$uri = $element->find('h3 a', 0)->href;
|
||||
|
@@ -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;
|
||||
|
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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,208 +0,0 @@
|
||||
<?php
|
||||
class GooglePlusPostBridge extends BridgeAbstract{
|
||||
|
||||
private $title;
|
||||
private $url;
|
||||
|
||||
const MAINTAINER = 'Grummfy, logmanoriginal';
|
||||
const NAME = 'Google Plus Post Bridge';
|
||||
const URI = 'https://plus.google.com';
|
||||
const CACHE_TIMEOUT = 600; //10min
|
||||
const DESCRIPTION = 'Returns user public post (without API).';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'username' => array(
|
||||
'name' => 'username or Id',
|
||||
'required' => true
|
||||
),
|
||||
'include_media' => array(
|
||||
'name' => 'Include media',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Enable to include media in the feed content'
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://ssl.gstatic.com/images/branding/product/ico/google_plus_alldp.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$username = $this->getInput('username');
|
||||
|
||||
// Usernames start with a + if it's not an ID
|
||||
if(!is_numeric($username) && substr($username, 0, 1) !== '+') {
|
||||
$username = '+' . $username;
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM(static::URI . '/' . urlencode($username) . '/posts')
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$this->title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
$this->url = $html->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||
|
||||
foreach($html->find('div[jsname=WsjYwc]') as $post) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['author'] = $post->find('div div div div a', 0)->innertext;
|
||||
$item['uri'] = $post->find('div div div a', 1)->href;
|
||||
|
||||
$timestamp = $post->find('a.qXj2He span', 0);
|
||||
|
||||
if($timestamp) {
|
||||
$item['timestamp'] = strtotime('+' . preg_replace(
|
||||
'/[^0-9A-Za-z]/',
|
||||
'',
|
||||
$timestamp->getAttribute('aria-label')));
|
||||
}
|
||||
|
||||
$message = $post->find('div[jsname=EjRJtf]', 0);
|
||||
|
||||
// Empty messages are not supported right now
|
||||
if(!$message) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['content'] = '<div style="float: left; padding: 0 10px 10px 0;"><a href="'
|
||||
. $this->url
|
||||
. '"><img align="top" alt="'
|
||||
. $item['author']
|
||||
. '" src="'
|
||||
. $post->find('div img', 0)->src
|
||||
. '" /></a></div><div>'
|
||||
. trim(strip_tags($message, '<a><p><div><img>'))
|
||||
. '</div>';
|
||||
|
||||
// Make title at least 50 characters long, but don't add '...' if it is shorter!
|
||||
if(strlen($message->plaintext) > 50) {
|
||||
$end = strpos($message->plaintext, ' ', 50) ?: strlen($message->plaintext);
|
||||
} else {
|
||||
$end = strlen($message->plaintext);
|
||||
}
|
||||
|
||||
if(strlen(substr($message->plaintext, 0, $end)) === strlen($message->plaintext)) {
|
||||
$item['title'] = $message->plaintext;
|
||||
} else {
|
||||
$item['title'] = substr($message->plaintext, 0, $end) . '...';
|
||||
}
|
||||
|
||||
$media = $post->find('[jsname="MTOxpb"]', 0);
|
||||
|
||||
if($media) {
|
||||
|
||||
$item['enclosures'] = array();
|
||||
|
||||
foreach($media->find('img') as $img) {
|
||||
$item['enclosures'][] = $this->fixImage($img)->src;
|
||||
}
|
||||
|
||||
if($this->getInput('include_media') === true && count($item['enclosures'] > 0)) {
|
||||
$item['content'] .= '<div style="clear: both;"><a href="'
|
||||
. $item['enclosures'][0]
|
||||
. '"><img src="'
|
||||
. $item['enclosures'][0]
|
||||
. '" /></a></div>';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Add custom parameters (only useful for JSON or Plaintext)
|
||||
$item['fullname'] = $item['author'];
|
||||
$item['avatar'] = $post->find('div img', 0)->src;
|
||||
$item['id'] = $post->find('div div div', 0)->getAttribute('id');
|
||||
$item['content_simple'] = $message->plaintext;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
return $this->title ?: 'Google Plus Post Bridge';
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
return $this->url ?: parent::getURI();
|
||||
}
|
||||
|
||||
private function fixImage($img) {
|
||||
|
||||
// There are certain images like .gif which link to a static picture and
|
||||
// get replaced dynamically via JS in the browser. If we want the "real"
|
||||
// image we need to account for that.
|
||||
|
||||
$urlparts = parse_url($img->src);
|
||||
|
||||
if(array_key_exists('host', $urlparts)) {
|
||||
|
||||
// For some reason some URIs don't contain the scheme, assume https
|
||||
if(!array_key_exists('scheme', $urlparts)) {
|
||||
$urlparts['scheme'] = 'https';
|
||||
}
|
||||
|
||||
$pathelements = explode('/', $urlparts['path']);
|
||||
|
||||
switch($urlparts['host']) {
|
||||
|
||||
case 'lh3.googleusercontent.com':
|
||||
|
||||
if(pathinfo(end($pathelements), PATHINFO_EXTENSION)) {
|
||||
|
||||
// The second to last element of the path specifies the
|
||||
// image format. The URL is still valid if we remove it.
|
||||
unset($pathelements[count($pathelements) - 2]);
|
||||
|
||||
} elseif(strrpos(end($pathelements), '=') !== false) {
|
||||
|
||||
// Some images go throug a proxy. For those images they
|
||||
// add size information after an equal sign.
|
||||
// Example: '=w530-h298-n'. Again this can safely be
|
||||
// removed to get the original image.
|
||||
$pathelements[count($pathelements) - 1] = substr(
|
||||
end($pathelements),
|
||||
0,
|
||||
strrpos(end($pathelements), '=')
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
$urlparts['path'] = implode('/', $pathelements);
|
||||
|
||||
}
|
||||
|
||||
$img->src = $this->build_url($urlparts);
|
||||
return $img;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* From: https://gist.github.com/Ellrion/f51ba0d40ae1d62eeae44fd1adf7b704
|
||||
* slightly adjusted to work with PHP < 7.0
|
||||
* @param array $parts
|
||||
* @return string
|
||||
*/
|
||||
private function build_url(array $parts)
|
||||
{
|
||||
|
||||
$scheme = isset($parts['scheme']) ? ($parts['scheme'] . '://') : '';
|
||||
$host = isset($parts['host']) ? $parts['host'] : '';
|
||||
$port = isset($parts['port']) ? (':' . $parts['port']) : '';
|
||||
$user = isset($parts['user']) ? $parts['user'] : '';
|
||||
$pass = isset($parts['pass']) ? (':' . $parts['pass']) : '';
|
||||
$pass = ($user || $pass) ? ($pass . '@') : '';
|
||||
$path = isset($parts['path']) ? $parts['path'] : '';
|
||||
$query = isset($parts['query']) ? ('?' . $parts['query']) : '';
|
||||
$fragment = isset($parts['fragment']) ? ('#' . $parts['fragment']) : '';
|
||||
|
||||
return implode('', [$scheme, $user, $pass, $host, $port, $path, $query, $fragment]);
|
||||
|
||||
}
|
||||
}
|
@@ -16,13 +16,13 @@ class HDWallpapersBridge extends BridgeAbstract {
|
||||
),
|
||||
'r' => array(
|
||||
'name' => 'resolution',
|
||||
'defaultValue' => '1920x1200',
|
||||
'exampleValue' => '1920x1200, 1680x1050,…'
|
||||
'defaultValue' => 'HD',
|
||||
'exampleValue' => 'HD, 1920x1200, 1680x1050,…'
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$category = $this->category;
|
||||
$category = $this->getInput('c');
|
||||
if(strrpos($category, 'wallpapers') !== strlen($category) - strlen('wallpapers')) {
|
||||
$category .= '-desktop-wallpapers';
|
||||
}
|
||||
@@ -45,13 +45,12 @@ class HDWallpapersBridge extends BridgeAbstract {
|
||||
$thumbnail = $element->find('img', 0);
|
||||
|
||||
$item = array();
|
||||
// http://www.hdwallpapers.in/download/yosemite_reflections-1680x1050.jpg
|
||||
$item['uri'] = self::URI
|
||||
. '/download'
|
||||
. str_replace('wallpapers.html', $this->getInput('r') . '.jpg', $element->href);
|
||||
|
||||
$item['timestamp'] = time();
|
||||
$item['title'] = $element->find('p', 0)->text();
|
||||
$item['title'] = $element->find('em1', 0)->text();
|
||||
$item['content'] = $item['title']
|
||||
. '<br><a href="'
|
||||
. $item['uri']
|
||||
@@ -60,6 +59,7 @@ class HDWallpapersBridge extends BridgeAbstract {
|
||||
. $thumbnail->src
|
||||
. '" /></a>';
|
||||
|
||||
$item['enclosures'] = array($item['uri']);
|
||||
$this->items[] = $item;
|
||||
|
||||
$num++;
|
||||
|
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
75
bridges/HeiseBridge.php
Normal file
75
bridges/HeiseBridge.php
Normal file
@@ -0,0 +1,75 @@
|
||||
<?php
|
||||
|
||||
class HeiseBridge extends FeedExpander {
|
||||
const MAINTAINER = 'Dreckiger-Dan';
|
||||
const NAME = 'Heise Online Bridge';
|
||||
const URI = 'https://heise.de/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns the full articles instead of only the intro';
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Alle News'
|
||||
=> 'https://www.heise.de/newsticker/heise-atom.xml',
|
||||
'Top-News'
|
||||
=> 'https://www.heise.de/newsticker/heise-top-atom.xml',
|
||||
'Internet-Störungen'
|
||||
=> 'https://www.heise.de/netze/netzwerk-tools/imonitor-internet-stoerungen/feed/aktuelle-meldungen/',
|
||||
'Alle News von heise Developer'
|
||||
=> 'https://www.heise.de/developer/rss/news-atom.xml'
|
||||
)
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify number of full articles to return',
|
||||
'defaultValue' => 5
|
||||
)
|
||||
));
|
||||
const LIMIT = 5;
|
||||
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas(
|
||||
$this->getInput('category'),
|
||||
$this->getInput('limit') ?: static::LIMIT
|
||||
);
|
||||
}
|
||||
|
||||
protected function parseItem($feedItem) {
|
||||
$item = parent::parseItem($feedItem);
|
||||
$uri = $item['uri'];
|
||||
|
||||
do {
|
||||
$article = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not open article: ' . $uri);
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
private function addArticleToItem($item, $article) {
|
||||
if($author = $article->find('[itemprop="author"]', 0))
|
||||
$item['author'] = $author->plaintext;
|
||||
|
||||
$content = $article->find('div[class*="article-content"]', 0);
|
||||
|
||||
foreach($content->find('p, h3, ul, table, pre, img') as $element) {
|
||||
$item['content'] .= $element;
|
||||
}
|
||||
|
||||
foreach($content->find('img') as $img) {
|
||||
$item['enclosures'][] = $img->src;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -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';
|
||||
|
||||
|
55
bridges/IGNBridge.php
Normal file
55
bridges/IGNBridge.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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);
|
||||
|
||||
/*
|
||||
* 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
|
||||
$uselessWikiElements = array(
|
||||
'.wiki-page-tools',
|
||||
'.feedback-container',
|
||||
'.paging-container'
|
||||
);
|
||||
foreach($articlePage->find('.wiki-page') as $wikiContents) {
|
||||
$copy = clone $wikiContents;
|
||||
// Remove useless elements present in IGN wiki/howtos
|
||||
foreach($uselessWikiElements as $uslElement) {
|
||||
$toRemove = $wikiContents->find($uslElement, 0);
|
||||
$copy = str_replace($toRemove, '', $copy);
|
||||
}
|
||||
$article = $article . $copy;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
@@ -42,6 +42,38 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
);
|
||||
|
||||
const USER_QUERY_HASH = '58b6785bea111c67129decbe6a448951';
|
||||
const TAG_QUERY_HASH = '174a5243287c5f3a7de741089750ab3b';
|
||||
const STORY_QUERY_HASH = '865589822932d1b43dfe312121dd353a';
|
||||
|
||||
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([$username]);
|
||||
$key = $cache->loadData();
|
||||
|
||||
if($key == null) {
|
||||
$data = getContents(self::URI . 'web/search/topsearch/?query=' . $username);
|
||||
|
||||
foreach(json_decode($data)->users as $user) {
|
||||
if($user->user->username === $username) {
|
||||
$key = $user->user->pk;
|
||||
}
|
||||
}
|
||||
if($key == null) {
|
||||
returnServerError('Unable to find username in search result.');
|
||||
}
|
||||
$cache->saveData($key);
|
||||
}
|
||||
return $key;
|
||||
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
if(is_null($this->getInput('u')) && $this->getInput('media_type') == 'story') {
|
||||
@@ -51,9 +83,9 @@ class InstagramBridge extends BridgeAbstract {
|
||||
$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;
|
||||
}
|
||||
@@ -89,7 +121,7 @@ class InstagramBridge extends BridgeAbstract {
|
||||
if (isset($media->edge_media_to_caption->edges[0]->node->text)) {
|
||||
$textContent = $media->edge_media_to_caption->edges[0]->node->text;
|
||||
} else {
|
||||
$textContent = basename($media->display_url);
|
||||
$textContent = '(no text)';
|
||||
}
|
||||
|
||||
$item['title'] = ($media->is_video ? '▶ ' : '') . trim($textContent);
|
||||
@@ -99,14 +131,16 @@ class InstagramBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
if(!is_null($this->getInput('u')) && $media->__typename == 'GraphSidecar') {
|
||||
|
||||
$data = $this->getInstagramStory($item['uri']);
|
||||
$item['content'] = $data[0];
|
||||
$item['enclosures'] = $data[1];
|
||||
} else {
|
||||
$mediaURI = self::URI . 'p/' . $media->shortcode . '/media?size=l';
|
||||
$item['content'] = '<a href="' . htmlentities($item['uri']) . '" target="_blank">';
|
||||
$item['content'] .= '<img src="' . htmlentities($media->display_url) . '" alt="' . $item['title'] . '" />';
|
||||
$item['content'] .= '<img src="' . htmlentities($mediaURI) . '" alt="' . $item['title'] . '" />';
|
||||
$item['content'] .= '</a><br><br>' . nl2br(htmlentities($textContent));
|
||||
$item['enclosures'] = array($media->display_url);
|
||||
$item['enclosures'] = array($mediaURI);
|
||||
}
|
||||
|
||||
$item['timestamp'] = $media->taken_at_timestamp;
|
||||
@@ -117,8 +151,15 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
protected function getInstagramStory($uri) {
|
||||
|
||||
$data = $this->getInstagramJSON($uri);
|
||||
$mediaInfo = $data->entry_data->PostPage[0]->graphql->shortcode_media;
|
||||
$shortcode = explode('/', $uri)[4];
|
||||
$data = getContents(self::URI .
|
||||
'graphql/query/?query_hash=' .
|
||||
self::STORY_QUERY_HASH .
|
||||
'&variables={"shortcode"%3A"' .
|
||||
$shortcode .
|
||||
'"}');
|
||||
|
||||
$mediaInfo = json_decode($data)->data->shortcode_media;
|
||||
|
||||
//Process the first element, that isn't in the node graph
|
||||
if (count($mediaInfo->edge_media_to_caption->edges) > 0) {
|
||||
@@ -144,13 +185,38 @@ class InstagramBridge extends BridgeAbstract {
|
||||
|
||||
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>';
|
||||
}
|
||||
}
|
||||
|
293
bridges/InternetArchiveBridge.php
Normal file
293
bridges/InternetArchiveBridge.php
Normal file
@@ -0,0 +1,293 @@
|
||||
<?php
|
||||
class InternetArchiveBridge extends BridgeAbstract {
|
||||
const NAME = 'Internet Archive Bridge';
|
||||
const URI = 'https://archive.org';
|
||||
const DESCRIPTION = 'Returns newest uploads, posts and more from an account';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'Account' => array(
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '@verifiedjoseph',
|
||||
),
|
||||
'content' => array(
|
||||
'name' => 'Content',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Uploads' => 'uploads',
|
||||
'Posts' => 'posts',
|
||||
'Reviews' => 'reviews',
|
||||
'Collections' => 'collections',
|
||||
'Web Archives' => 'web-archive',
|
||||
),
|
||||
'defaultValue' => 'uploads',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 900; // 15 mins
|
||||
|
||||
private $skipClasses = array(
|
||||
'item-ia mobile-header hidden-tiles',
|
||||
'item-ia account-ia'
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
if ($this->getInput('content') !== 'posts') {
|
||||
|
||||
$detailsDivNumber = 0;
|
||||
|
||||
foreach ($html->find('div.results > div[data-id]') as $index => $result) {
|
||||
$item = array();
|
||||
|
||||
if (in_array($result->class, $this->skipClasses)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
switch($result->class) {
|
||||
case 'item-ia':
|
||||
|
||||
switch($this->getInput('content')) {
|
||||
case 'reviews':
|
||||
$item = $this->processReview($result);
|
||||
break;
|
||||
case 'uploads':
|
||||
$item = $this->processUpload($result);
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'item-ia url-item':
|
||||
$item = $this->processWebArchives($result);
|
||||
break;
|
||||
case 'item-ia collection-ia':
|
||||
$item = $this->processCollection($result);
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->getInput('content') !== 'reviews') {
|
||||
$hiddenDetails = $this->processHiddenDetails($html, $detailsDivNumber, $item);
|
||||
|
||||
$this->items[] = array_merge($item, $hiddenDetails);
|
||||
} else {
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
$detailsDivNumber++;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->getInput('content') === 'posts') {
|
||||
$this->items = $this->processPosts($html);
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('username')) && !is_null($this->getInput('content'))) {
|
||||
return self::URI . '/details/' . $this->processUsername() . '&tab=' . $this->getInput('content');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('username')) && !is_null($this->getInput('content'))) {
|
||||
|
||||
$contentValues = array_flip(self::PARAMETERS['Account']['content']['values']);
|
||||
|
||||
return $contentValues[$this->getInput('content')] . ' - '
|
||||
. $this->processUsername() . ' - Internet Archive';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function processUsername() {
|
||||
|
||||
if (substr($this->getInput('username'), 0, 1) !== '@') {
|
||||
return '@' . $this->getInput('username');
|
||||
}
|
||||
|
||||
return $this->getInput('username');
|
||||
}
|
||||
|
||||
private function processUpload($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$collection = $result->find('a.stealth', 0);
|
||||
$collectionLink = self::URI . $collection->href;
|
||||
$collectionTitle = $collection->find('div.item-parent-ttl', 0)->plaintext;
|
||||
|
||||
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
|
||||
$item['timestamp'] = strtotime($result->find('div.hidden-tiles.pubdate.C.C3', 0)->children(0)->plaintext);
|
||||
$item['uri'] = $result->find('div.item-ttl.C.C2 > a', 0)->href;
|
||||
|
||||
if ($result->find('div.by.C.C4', 0)->children(2)) {
|
||||
$item['author'] = $result->find('div.by.C.C4', 0)->children(2)->plaintext;
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
<p>Media Type: {$result->attr['data-mediatype']}<br>
|
||||
Collection: <a href="{$collectionLink}">{$collectionTitle}</a></p>
|
||||
EOD;
|
||||
|
||||
$item['enclosures'][] = self::URI . $result->find('img.item-img', 0)->source;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function processReview($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
|
||||
$item['timestamp'] = strtotime($result->find('div.hidden-tiles.pubdate.C.C3', 0)->children(0)->plaintext);
|
||||
$item['uri'] = $result->find('div.review-title', 0)->children(0)->href;
|
||||
|
||||
if ($result->find('div.by.C.C4', 0)->children(2)) {
|
||||
$item['author'] = $result->find('div.by.C.C4', 0)->children(2)->plaintext;
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
<p><strong>Subject: {$result->find('div.review-title', 0)->plaintext}</strong></p>
|
||||
<p>{$result->find('div.hidden-lists.review' , 0)->children(1)->plaintext}</p>
|
||||
EOD;
|
||||
|
||||
$item['enclosures'][] = self::URI . $result->find('img.item-img', 0)->source;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function processWebArchives($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = trim($result->find('div.ttl', 0)->plaintext);
|
||||
$item['timestamp'] = strtotime($result->find('div.hidden-lists', 0)->children(0)->plaintext);
|
||||
$item['uri'] = $result->find('div.item-ttl.C.C2 > a', 0)->href;
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
{$this->processUsername()} archived <a href="{$item['uri']}">{$result->find('div.ttl', 0)->plaintext}</a>
|
||||
EOD;
|
||||
|
||||
$item['enclosures'][] = $result->find('img.item-img', 0)->source;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function processCollection($result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$title = trim($result->find('div.collection-title.C.C2', 0)->children(0)->plaintext);
|
||||
$itemCount = strtolower(trim($result->find('div.num-items.topinblock', 0)->plaintext));
|
||||
|
||||
$item['title'] = $title . ' (' . $itemCount . ')';
|
||||
$item['timestamp'] = strtotime($result->find('div.hidden-tiles.pubdate.C.C3', 0)->children(0)->plaintext);
|
||||
$item['uri'] = $result->find('div.collection-title.C.C2 > a', 0)->href;
|
||||
|
||||
$item['content'] = '';
|
||||
|
||||
if ($result->find('img.item-img', 0)) {
|
||||
$item['enclosures'][] = self::URI . $result->find('img.item-img', 0)->source;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function processHiddenDetails($html, $detailsDivNumber, $item) {
|
||||
|
||||
$description = '';
|
||||
|
||||
if ($html->find('div.details-ia.hidden-tiles', $detailsDivNumber)) {
|
||||
$detailsDiv = $html->find('div.details-ia.hidden-tiles', $detailsDivNumber);
|
||||
|
||||
if ($detailsDiv->find('div.C234', 0)->children(0)) {
|
||||
$description = $detailsDiv->find('div.C234', 0)->children(0)->plaintext;
|
||||
|
||||
$detailsDiv->find('div.C234', 0)->children(0)->innertext = '';
|
||||
}
|
||||
|
||||
$topics = trim($detailsDiv->find('div.C234', 0)->plaintext);
|
||||
|
||||
if (!empty($topics)) {
|
||||
$topics = trim($detailsDiv->find('div.C234', 0)->plaintext);
|
||||
$topics = trim(substr($topics, 7));
|
||||
|
||||
$item['categories'] = explode(',', $topics);
|
||||
}
|
||||
|
||||
$item['content'] = '<p>' . $description . '</p>' . $item['content'];
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function processPosts($html) {
|
||||
|
||||
$items = array();
|
||||
|
||||
foreach ($html->find('table.forumTable > tr') as $index => $tr) {
|
||||
$item = array();
|
||||
|
||||
if ($index === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = $tr->find('td', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($tr->find('td', 4)->children(0)->plaintext);
|
||||
$item['uri'] = $tr->find('td', 0)->children(0)->href;
|
||||
|
||||
$formLink = <<<EOD
|
||||
<a href="{$tr->find('td', 2)->children(0)->href}">{$tr->find('td', 2)->children(0)->plaintext}</a>
|
||||
EOD;
|
||||
|
||||
$postDate = $tr->find('td', 4)->children(0)->plaintext;
|
||||
|
||||
$postPageHtml = getSimpleHTMLDOMCached($item['uri'], 3600)
|
||||
or returnServerError('Could not request: ' . $item['uri']);
|
||||
|
||||
$postPageHtml = defaultLinkTo($postPageHtml, $this->getURI());
|
||||
|
||||
$post = $postPageHtml->find('div.box.well.well-sm', 0);
|
||||
|
||||
$parentLink = '';
|
||||
$replyLink = <<<EOD
|
||||
<a href="{$post->find('a', 0)->href}">Reply</a>
|
||||
EOD;
|
||||
|
||||
if ($post->find('a', 1)->innertext = 'See parent post') {
|
||||
$parentLink = <<<EOD
|
||||
<a href="{$post->find('a', 1)->href}">View parent post</a>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$post->find('h1', 0)->outertext = '';
|
||||
$post->find('h2', 0)->outertext = '';
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
<p>{$post->innertext}</p>{$replyLink} - {$parentLink} - Posted in {$formLink} on {$postDate}
|
||||
EOD;
|
||||
|
||||
$items[] = $item;
|
||||
|
||||
if (count($items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
128
bridges/IvooxBridge.php
Normal file
128
bridges/IvooxBridge.php
Normal file
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* IvooxRssBridge
|
||||
* Returns the latest search result
|
||||
* TODO: support podcast episodes list
|
||||
*/
|
||||
class IvooxBridge extends BridgeAbstract {
|
||||
const NAME = 'Ivoox Bridge';
|
||||
const URI = 'https://www.ivoox.com/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const DESCRIPTION = 'Returns the 10 newest episodes by keyword search';
|
||||
const MAINTAINER = 'xurxof'; // based on YoutubeBridge by mitsukarenai
|
||||
const PARAMETERS = array(
|
||||
'Search result' => array(
|
||||
's' => array(
|
||||
'name' => 'keyword',
|
||||
'exampleValue' => 'test'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function ivBridgeAddItem(
|
||||
$episode_link,
|
||||
$podcast_name,
|
||||
$episode_title,
|
||||
$author_name,
|
||||
$episode_description,
|
||||
$publication_date,
|
||||
$episode_duration) {
|
||||
$item = array();
|
||||
$item['title'] = htmlspecialchars_decode($podcast_name . ': ' . $episode_title);
|
||||
$item['author'] = $author_name;
|
||||
$item['timestamp'] = $publication_date;
|
||||
$item['uri'] = $episode_link;
|
||||
$item['content'] = '<a href="' . $episode_link . '">' . $podcast_name . ': ' . $episode_title
|
||||
. '</a><br />Duration: ' . $episode_duration
|
||||
. '<br />Description:<br />' . $episode_description;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function ivBridgeParseHtmlListing($html) {
|
||||
$limit = 4;
|
||||
$count = 0;
|
||||
|
||||
foreach($html->find('div.flip-container') as $flipper) {
|
||||
$linkcount = 0;
|
||||
if(!empty($flipper->find( 'div.modulo-type-banner' ))) {
|
||||
// ad
|
||||
continue;
|
||||
}
|
||||
|
||||
if($count < $limit) {
|
||||
foreach($flipper->find('div.header-modulo') as $element) {
|
||||
foreach($element->find('a') as $link) {
|
||||
if ($linkcount == 0) {
|
||||
$episode_link = $link->href;
|
||||
$episode_title = $link->title;
|
||||
} elseif ($linkcount == 1) {
|
||||
$author_link = $link->href;
|
||||
$author_name = $link->title;
|
||||
} elseif ($linkcount == 2) {
|
||||
$podcast_link = $link->href;
|
||||
$podcast_name = $link->title;
|
||||
}
|
||||
|
||||
$linkcount++;
|
||||
}
|
||||
}
|
||||
|
||||
$episode_description = $flipper->find('button.btn-link', 0)->getAttribute('data-content');
|
||||
$episode_duration = $flipper->find('p.time', 0)->innertext;
|
||||
$publication_date = $flipper->find('li.date', 0)->getAttribute('title');
|
||||
|
||||
// alternative date_parse_from_format
|
||||
// or DateTime::createFromFormat('G:i - d \d\e M \d\e Y', $publication);
|
||||
// TODO: month name translations, due function doesn't support locale
|
||||
|
||||
$a = strptime($publication_date, '%H:%M - %d de %b. de %Y'); // obsolete function, uses c libraries
|
||||
$publication_date = mktime(0, 0, 0, $a['tm_mon'] + 1, $a['tm_mday'], $a['tm_year'] + 1900);
|
||||
|
||||
$this->ivBridgeAddItem(
|
||||
$episode_link,
|
||||
$podcast_name,
|
||||
$episode_title,
|
||||
$author_name,
|
||||
$episode_description,
|
||||
$publication_date,
|
||||
$episode_duration
|
||||
);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
// store locale, change to spanish
|
||||
$originalLocales = explode(';', setlocale(LC_ALL, 0));
|
||||
setlocale(LC_ALL, 'es_ES.utf8');
|
||||
|
||||
$xml = '';
|
||||
$html = '';
|
||||
$url_feed = '';
|
||||
if($this->getInput('s')) { /* Search modes */
|
||||
$this->request = str_replace(' ', '-', $this->getInput('s'));
|
||||
$url_feed = self::URI . urlencode($this->request) . '_sb_f_1.html?o=uploaddate';
|
||||
} else {
|
||||
returnClientError('Not valid mode at IvooxBridge');
|
||||
}
|
||||
|
||||
$dom = getSimpleHTMLDOM($url_feed)
|
||||
or returnServerError('Could not request ' . $url_feed);
|
||||
$this->ivBridgeParseHtmlListing($dom);
|
||||
|
||||
// restore locale
|
||||
|
||||
foreach($originalLocales as $localeSetting) {
|
||||
if(strpos($localeSetting, '=') !== false) {
|
||||
list($category, $locale) = explode('=', $localeSetting);
|
||||
} else {
|
||||
$category = LC_ALL;
|
||||
$locale = $localeSetting;
|
||||
}
|
||||
|
||||
setlocale($category, $locale);
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,6 +24,16 @@ class KununuBridge extends BridgeAbstract {
|
||||
'type' => 'checkbox',
|
||||
'exampleValue' => 'checked',
|
||||
'title' => 'Activate to load full article'
|
||||
),
|
||||
'include_ratings' => array(
|
||||
'name' => 'Include ratings',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to include ratings in the feed'
|
||||
),
|
||||
'include_benefits' => array(
|
||||
'name' => 'Include benefits',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to include benefits in the feed'
|
||||
)
|
||||
),
|
||||
array(
|
||||
@@ -116,7 +126,7 @@ class KununuBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
|
||||
$item['author'] = $this->extractArticleAuthorPosition($article);
|
||||
$item['timestamp'] = strtotime($date);
|
||||
$item['timestamp'] = strtotime($date->content);
|
||||
$item['title'] = $rating->getAttribute('aria-label')
|
||||
. ' : '
|
||||
. strip_tags($summary->innertext);
|
||||
@@ -175,7 +185,32 @@ class KununuBridge extends BridgeAbstract {
|
||||
$description = $article->find('[itemprop=reviewBody]', 0)
|
||||
or returnServerError('Cannot find article description!');
|
||||
|
||||
return $description->innertext;
|
||||
$retVal = $description->innertext;
|
||||
|
||||
if($this->getInput('include_ratings')
|
||||
&& ($ratings = $article->find('.review-ratings .rating-group'))) {
|
||||
$retVal .= (empty($retVal) ? '' : '<hr>') . '<table>';
|
||||
foreach($ratings as $rating) {
|
||||
$retVal .= <<<EOD
|
||||
<tr>
|
||||
<td>{$rating->find('.rating-title', 0)->plaintext}
|
||||
<td>{$rating->find('.rating-badge', 0)->plaintext}
|
||||
</tr>
|
||||
EOD;
|
||||
}
|
||||
$retVal .= '</table>';
|
||||
}
|
||||
|
||||
if($this->getInput('include_benefits')
|
||||
&& ($benefits = $article->find('benefit'))) {
|
||||
$retVal .= (empty($retVal) ? '' : '<hr>') . '<ul>';
|
||||
foreach($benefits as $benefit) {
|
||||
$retVal .= "<li>{$benefit->plaintext}</li>";
|
||||
}
|
||||
$retVal .= '</ul>';
|
||||
}
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
|
477
bridges/LaCentraleBridge.php
Normal file
477
bridges/LaCentraleBridge.php
Normal file
@@ -0,0 +1,477 @@
|
||||
<?php
|
||||
class LaCentraleBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'jacknumber';
|
||||
const NAME = 'La Centrale';
|
||||
const URI = 'https://www.lacentrale.fr/';
|
||||
const DESCRIPTION = 'Returns most recent vehicules ads from LaCentrale';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'type' => array(
|
||||
'name' => 'Type de véhicule',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Voiture' => 'car',
|
||||
'Camion/Pickup' => 'truck',
|
||||
'Moto' => 'moto',
|
||||
'Scooter' => 'scooter',
|
||||
'Quad' => 'quad',
|
||||
'Caravane/Camping-car' => 'mobileHome'
|
||||
)
|
||||
),
|
||||
'brand' => array(
|
||||
'name' => 'Marque',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'ABARTH' => 'ABARTH',
|
||||
'AC' => 'AC',
|
||||
'AIXAM' => 'AIXAM',
|
||||
'ALFA ROMEO' => 'ALFA ROMEO',
|
||||
'ALKE' => 'ALKE',
|
||||
'ALPINA' => 'ALPINA',
|
||||
'ALPINE' => 'ALPINE',
|
||||
'AMC' => 'AMC',
|
||||
'ANAIG' => 'ANAIG',
|
||||
'APRILIA' => 'APRILIA',
|
||||
'ARIEL' => 'ARIEL',
|
||||
'ASTON MARTIN' => 'ASTON MARTIN',
|
||||
'AUDI' => 'AUDI',
|
||||
'AUSTIN HEALEY' => 'AUSTIN HEALEY',
|
||||
'AUSTIN' => 'AUSTIN',
|
||||
'AUTOBIANCHI' => 'AUTOBIANCHI',
|
||||
'AVINTON' => 'AVINTON',
|
||||
'BELLIER' => 'BELLIER',
|
||||
'BENELLI' => 'BENELLI',
|
||||
'BENTLEY' => 'BENTLEY',
|
||||
'BETA' => 'BETA',
|
||||
'BMW' => 'BMW',
|
||||
'BOLLORE' => 'BOLLORE',
|
||||
'BRIXTON' => 'BRIXTON',
|
||||
'BUELL' => 'BUELL',
|
||||
'BUGATTI' => 'BUGATTI',
|
||||
'BUICK' => 'BUICK',
|
||||
'BULLIT' => 'BULLIT',
|
||||
'CADILLAC' => 'CADILLAC',
|
||||
'CASALINI' => 'CASALINI',
|
||||
'CATERHAM' => 'CATERHAM',
|
||||
'CHATENET' => 'CHATENET',
|
||||
'CHEVROLET' => 'CHEVROLET',
|
||||
'CHRYSLER' => 'CHRYSLER',
|
||||
'CHUNLAN' => 'CHUNLAN',
|
||||
'CITROEN' => 'CITROEN',
|
||||
'COURB' => 'COURB',
|
||||
'CR&S' => 'CR&S',
|
||||
'CUPRA' => 'CUPRA',
|
||||
'CYCLONE' => 'CYCLONE',
|
||||
'DACIA' => 'DACIA',
|
||||
'DAELIM' => 'DAELIM',
|
||||
'DAEWOO' => 'DAEWOO',
|
||||
'DAF' => 'DAF',
|
||||
'DAIHATSU' => 'DAIHATSU',
|
||||
'DANGEL' => 'DANGEL',
|
||||
'DATSUN' => 'DATSUN',
|
||||
'DE SOTO' => 'DE SOTO',
|
||||
'DE TOMASO' => 'DE TOMASO',
|
||||
'DERBI' => 'DERBI',
|
||||
'DEVINCI' => 'DEVINCI',
|
||||
'DODGE' => 'DODGE',
|
||||
'DONKERVOORT' => 'DONKERVOORT',
|
||||
'DS' => 'DS',
|
||||
'DUCATI' => 'DUCATI',
|
||||
'DUCATY' => 'DUCATY',
|
||||
'DUE' => 'DUE',
|
||||
'ENFIELD' => 'ENFIELD',
|
||||
'EXCALIBUR' => 'EXCALIBUR',
|
||||
'FACEL VEGA' => 'FACEL VEGA',
|
||||
'FANTIC MOTOR' => 'FANTIC MOTOR',
|
||||
'FERRARI' => 'FERRARI',
|
||||
'FIAT' => 'FIAT',
|
||||
'FISKER' => 'FISKER',
|
||||
'FORD' => 'FORD',
|
||||
'FUSO' => 'FUSO',
|
||||
'GAS GAS' => 'GAS GAS',
|
||||
'GILERA' => 'GILERA',
|
||||
'GMC' => 'GMC',
|
||||
'GOWINN' => 'GOWINN',
|
||||
'GRANDIN' => 'GRANDIN',
|
||||
'HARLEY DAVIDSON' => 'HARLEY DAVIDSON',
|
||||
'HOMMELL' => 'HOMMELL',
|
||||
'HONDA' => 'HONDA',
|
||||
'HUMMER' => 'HUMMER',
|
||||
'HUSABERG' => 'HUSABERG',
|
||||
'HUSQVARNA' => 'HUSQVARNA',
|
||||
'HYOSUNG' => 'HYOSUNG',
|
||||
'HYUNDAI' => 'HYUNDAI',
|
||||
'INDIAN' => 'INDIAN',
|
||||
'INFINITI' => 'INFINITI',
|
||||
'INNOCENTI' => 'INNOCENTI',
|
||||
'ISUZU' => 'ISUZU',
|
||||
'IVECO' => 'IVECO',
|
||||
'JAGUAR' => 'JAGUAR',
|
||||
'JDM SIMPA' => 'JDM SIMPA',
|
||||
'JEEP' => 'JEEP',
|
||||
'JENSEN' => 'JENSEN',
|
||||
'JIAYUAN' => 'JIAYUAN',
|
||||
'KAWASAKI' => 'KAWASAKI',
|
||||
'KEEWAY' => 'KEEWAY',
|
||||
'KIA' => 'KIA',
|
||||
'KSR' => 'KSR',
|
||||
'KTM' => 'KTM',
|
||||
'KYMCO' => 'KYMCO',
|
||||
'LADA' => 'LADA',
|
||||
'LAMBORGHINI' => 'LAMBORGHINI',
|
||||
'LANCIA' => 'LANCIA',
|
||||
'LAND ROVER' => 'LAND ROVER',
|
||||
'LEXUS' => 'LEXUS',
|
||||
'LIGIER' => 'LIGIER',
|
||||
'LINCOLN' => 'LINCOLN',
|
||||
'LONDON TAXI COMPANY' => 'LONDON TAXI COMPANY',
|
||||
'LOTUS' => 'LOTUS',
|
||||
'MAGPOWER' => 'MAGPOWER',
|
||||
'MAN' => 'MAN',
|
||||
'MASAI' => 'MASAI',
|
||||
'MASERATI' => 'MASERATI',
|
||||
'MASH' => 'MASH',
|
||||
'MATRA' => 'MATRA',
|
||||
'MAYBACH' => 'MAYBACH',
|
||||
'MAZDA' => 'MAZDA',
|
||||
'MCLAREN' => 'MCLAREN',
|
||||
'MEGA' => 'MEGA',
|
||||
'MERCEDES' => 'MERCEDES',
|
||||
'MERCEDES-AMG' => 'MERCEDES-AMG',
|
||||
'MERCURY' => 'MERCURY',
|
||||
'MEYERS MANX' => 'MEYERS MANX',
|
||||
'MG' => 'MG',
|
||||
'MIA ELECTRIC' => 'MIA ELECTRIC',
|
||||
'MICROCAR' => 'MICROCAR',
|
||||
'MINAUTO' => 'MINAUTO',
|
||||
'MINI' => 'MINI',
|
||||
'MITSUBISHI' => 'MITSUBISHI',
|
||||
'MORGAN' => 'MORGAN',
|
||||
'MORRIS' => 'MORRIS',
|
||||
'MOTO GUZZI' => 'MOTO GUZZI',
|
||||
'MOTO MORINI' => 'MOTO MORINI',
|
||||
'MOTOBECANE' => 'MOTOBECANE',
|
||||
'MPM MOTORS' => 'MPM MOTORS',
|
||||
'MV AGUSTA' => 'MV AGUSTA',
|
||||
'NISSAN' => 'NISSAN',
|
||||
'NORTON' => 'NORTON',
|
||||
'NSU' => 'NSU',
|
||||
'OLDSMOBILE' => 'OLDSMOBILE',
|
||||
'OPEL' => 'OPEL',
|
||||
'ORCAL' => 'ORCAL',
|
||||
'OSSA' => 'OSSA',
|
||||
'PACKARD' => 'PACKARD',
|
||||
'PANTHER' => 'PANTHER',
|
||||
'PEUGEOT' => 'PEUGEOT',
|
||||
'PGO' => 'PGO',
|
||||
'PIAGGIO' => 'PIAGGIO',
|
||||
'PLYMOUTH' => 'PLYMOUTH',
|
||||
'POLARIS' => 'POLARIS',
|
||||
'PONTIAC' => 'PONTIAC',
|
||||
'PORSCHE' => 'PORSCHE',
|
||||
'REALM' => 'REALM',
|
||||
'REGAL RAPTOR' => 'REGAL RAPTOR',
|
||||
'RENAULT' => 'RENAULT',
|
||||
'RIEJU' => 'RIEJU',
|
||||
'ROLLS ROYCE' => 'ROLLS ROYCE',
|
||||
'ROVER' => 'ROVER',
|
||||
'ROYAL ENFIELD' => 'ROYAL ENFIELD',
|
||||
'SAAB' => 'SAAB',
|
||||
'SANTANA' => 'SANTANA',
|
||||
'SCANIA' => 'SCANIA',
|
||||
'SEAT' => 'SEAT',
|
||||
'SECMA' => 'SECMA',
|
||||
'SHELBY' => 'SHELBY',
|
||||
'SHERCO' => 'SHERCO',
|
||||
'SIMCA' => 'SIMCA',
|
||||
'SKODA' => 'SKODA',
|
||||
'SMART' => 'SMART',
|
||||
'SPYKER' => 'SPYKER',
|
||||
'SSANGYONG' => 'SSANGYONG',
|
||||
'STUDEBAKER' => 'STUDEBAKER',
|
||||
'SUBARU' => 'SUBARU',
|
||||
'SUNBEAM' => 'SUNBEAM',
|
||||
'SUZUKI' => 'SUZUKI',
|
||||
'SWM' => 'SWM',
|
||||
'SYM' => 'SYM',
|
||||
'TALBOT SIMCA' => 'TALBOT SIMCA',
|
||||
'TALBOT' => 'TALBOT',
|
||||
'TEILHOL' => 'TEILHOL',
|
||||
'TESLA' => 'TESLA',
|
||||
'TM' => 'TM',
|
||||
'TNT MOTOR' => 'TNT MOTOR',
|
||||
'TOYOTA' => 'TOYOTA',
|
||||
'TRIUMPH' => 'TRIUMPH',
|
||||
'TVR' => 'TVR',
|
||||
'VAUXHALL' => 'VAUXHALL',
|
||||
'VESPA' => 'VESPA',
|
||||
'VICTORY' => 'VICTORY',
|
||||
'VOLKSWAGEN' => 'VOLKSWAGEN',
|
||||
'VOLVO' => 'VOLVO',
|
||||
'VOXAN' => 'VOXAN',
|
||||
'WIESMANN' => 'WIESMANN',
|
||||
'YAMAHA' => 'YAMAHA',
|
||||
'YCF' => 'YCF',
|
||||
'ZERO' => 'ZERO',
|
||||
'ZONGSHEN' => 'ZONGSHEN'
|
||||
)
|
||||
),
|
||||
'model' => array(
|
||||
'name' => 'Modèle',
|
||||
'type' => 'text',
|
||||
'title' => 'Get the exact name on LaCentrale'
|
||||
),
|
||||
'versions' => array(
|
||||
'name' => 'Version(s)',
|
||||
'type' => 'text',
|
||||
'title' => 'Get the exact name(s) on LaCentrale. Separate by comma'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Catégorie',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Voiture' => array(
|
||||
'4x4, SUV & Crossover' => '47',
|
||||
'Citadine' => '40',
|
||||
'Berline' => '41_42',
|
||||
'Break' => '43',
|
||||
'Cabriolet' => '46',
|
||||
'Coupé' => '45',
|
||||
'Monospace' => '44',
|
||||
'Bus et minibus' => '82',
|
||||
'Fourgonnette' => '85',
|
||||
'Fourgon (< 3,5 tonnes)' => '81',
|
||||
'Pick-up' => '50',
|
||||
'Voiture société, commerciale' => '80',
|
||||
'Sans permis' => '48',
|
||||
'Camion (> 3,5 tonnes)' => '83',
|
||||
),
|
||||
'Camion/Pickup' => array(
|
||||
'Camion (> 3,5 tonnes)' => '83',
|
||||
'Fourgon (< 3,5 tonnes)' => '81',
|
||||
'Bus et minibus' => '82',
|
||||
'Fourgonnette' => '85',
|
||||
'Pick-up' => '50',
|
||||
'Voiture société, commerciale' => '80'
|
||||
),
|
||||
'Moto' => array(
|
||||
'Custom' => '60',
|
||||
'Offroad' => '61',
|
||||
'Roadster' => '62',
|
||||
'GT' => '63',
|
||||
'Mini moto' => '64',
|
||||
'Mobylette' => '65',
|
||||
'Supermotard' => '66',
|
||||
'Trail' => '67',
|
||||
'Side-car' => '69',
|
||||
'Sportive' => '68'
|
||||
),
|
||||
'Caravane/Camping-car' => array(
|
||||
'Caravane' => '423',
|
||||
'Profilé' => '506',
|
||||
'Fourgon aménagé' => '507',
|
||||
'Intégral' => '508',
|
||||
'Capucine' => '510'
|
||||
)
|
||||
)
|
||||
),
|
||||
'pricemin' => array(
|
||||
'name' => 'Prix min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'pricemax' => array(
|
||||
'name' => 'Prix max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'location' => array(
|
||||
'name' => 'CP ou département',
|
||||
'type' => 'number',
|
||||
'title' => 'Only one'
|
||||
),
|
||||
'distance' => array(
|
||||
'name' => 'Rayon de recherche',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'10 km' => '1',
|
||||
'20 km' => '2',
|
||||
'50 km' => '3',
|
||||
'100 km' => '4',
|
||||
'200 km' => '5'
|
||||
)
|
||||
),
|
||||
'region' => array(
|
||||
'name' => 'Région',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Auvergne-Rhône-Alpes' => 'FR-ARA',
|
||||
'Bourgogne-Franche-Comté' => 'FR-BFC',
|
||||
'Bretagne' => 'FR-BRE',
|
||||
'Centre-Val de Loire' => 'FR-CVL',
|
||||
'Corse' => 'FR-COR',
|
||||
'Grand Est' => 'FR-GES',
|
||||
'Hauts-de-France' => 'FR-HDF',
|
||||
'Île-de-France' => 'FR-IDF',
|
||||
'Normandie' => 'FR-NOR',
|
||||
'Nouvelle-Aquitaine' => 'FR-PAC',
|
||||
'Occitanie' => 'FR-PDL',
|
||||
'Pays de la Loire' => 'FR-OCC',
|
||||
'Provence-Alpes-Côte d\'Azur' => 'FR-NAQ'
|
||||
)
|
||||
),
|
||||
'mileagemin' => array(
|
||||
'name' => 'Kilométrage min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'mileagemax' => array(
|
||||
'name' => 'Kilométrage max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'yearmin' => array(
|
||||
'name' => 'Année min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'yearmax' => array(
|
||||
'name' => 'Année max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'cubiccapacitymin' => array(
|
||||
'name' => 'Cylindrée min',
|
||||
'type' => 'number'
|
||||
),
|
||||
'cubiccapacitymax' => array(
|
||||
'name' => 'Cylindrée max',
|
||||
'type' => 'number'
|
||||
),
|
||||
'fuel' => array(
|
||||
'name' => 'Énergie',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Diesel' => 'dies',
|
||||
'Essence' => 'ess',
|
||||
'Électrique' => 'elec',
|
||||
'Hybride' => 'hyb',
|
||||
'GPL' => 'gpl',
|
||||
'Bioéthanol' => 'eth',
|
||||
'Autre' => 'alt'
|
||||
)
|
||||
),
|
||||
'gearbox' => array(
|
||||
'name' => 'Boite de vitesse',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Boite automatique' => 'AUTO',
|
||||
'Boite mécanique' => 'MANUAL'
|
||||
)
|
||||
),
|
||||
'doors' => array(
|
||||
'name' => 'Nombre de portes',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'2 portes' => '2',
|
||||
'3 portes' => '3',
|
||||
'4 portes' => '4',
|
||||
'5 portes' => '5',
|
||||
'6 portes ou plus' => '6'
|
||||
)
|
||||
),
|
||||
'firsthand' => array(
|
||||
'name' => 'Première main',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'seller' => array(
|
||||
'name' => 'Vendeur',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Particulier' => 'PART',
|
||||
'Professionel' => 'PRO'
|
||||
)
|
||||
),
|
||||
'sort' => array(
|
||||
'name' => 'Tri',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Prix (croissant)' => 'priceAsc',
|
||||
'Prix (décroissant)' => 'priceDesc',
|
||||
'Marque (croissant)' => 'makeAsc',
|
||||
'Marque (décroissant)' => 'makeDesc',
|
||||
'Kilométrage (croissant)' => 'mileageAsc',
|
||||
'Kilométrage (décroissant)' => 'mileageDesc',
|
||||
'Année (croissant)' => 'yearAsc',
|
||||
'Année (décroissant)' => 'yearDesc',
|
||||
'Département (croissant)' => 'visitPlaceAsc',
|
||||
'Département (décroissant)' => 'visitPlaceDesc'
|
||||
)
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
// check data
|
||||
if(!empty($this->getInput('distance'))
|
||||
&& is_null($this->getInput('location'))) {
|
||||
returnClientError('You need a place ("CP ou département") to search arround.');
|
||||
}
|
||||
|
||||
$params = array(
|
||||
'vertical' => $this->getInput('type'),
|
||||
'makesModelsCommercialNames' => $this->getInput('brand') . ':' . $this->getInput('model'),
|
||||
'versions' => $this->getInput('versions'),
|
||||
'categories' => $this->getInput('category'),
|
||||
'priceMin' => $this->getInput('pricemin'),
|
||||
'priceMax' => $this->getInput('pricemax'),
|
||||
'dptCp' => $this->getInput('location'),
|
||||
'distance' => $this->getInput('distance'),
|
||||
'regions' => $this->getInput('region'),
|
||||
'mileageMin' => $this->getInput('mileagemin'),
|
||||
'mileageMax' => $this->getInput('mileagemax'),
|
||||
'yearMin' => $this->getInput('yearmin'),
|
||||
'yearMax' => $this->getInput('yearmax'),
|
||||
'cubicMin' => $this->getInput('cubiccapacitymin'),
|
||||
'cubicMax' => $this->getInput('cubiccapacitymax'),
|
||||
'energies' => $this->getInput('fuel'),
|
||||
'firstHand' => $this->getInput('firsthand') ? 'true' : 'false',
|
||||
'gearbox' => $this->getInput('gearbox'),
|
||||
'doors' => $this->getInput('doors'),
|
||||
'sortBy' => $this->getInput('sort')
|
||||
);
|
||||
$url = self::URI . 'listing?' . http_build_query($params);
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request LaCentrale.');
|
||||
|
||||
foreach($html->find('.linkAd') as $element) {
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = trim(self::URI, '/') . $element->href;
|
||||
$item['title'] = $element->find('.brandModel', 0)->plaintext;
|
||||
$item['sellerType'] = $element->find('.typeSeller', 0)->plaintext;
|
||||
$item['author'] = $item['sellerType'];
|
||||
$item['version'] = $element->find('.version', 0)->plaintext;
|
||||
$item['price'] = $element->find('.fieldPrice', 0)->plaintext;
|
||||
$item['year'] = $element->find('.fieldYear', 0)->plaintext;
|
||||
$item['mileage'] = $element->find('.fieldMileage', 0)->plaintext;
|
||||
$item['departement'] = str_replace(',', '', $element->find('.dptCont', 0)->plaintext);
|
||||
$item['thumbnail'] = $element->find('.imgContent img', 0)->src;
|
||||
$item['enclosures'] = array($item['thumbnail']);
|
||||
|
||||
$item['content'] = '
|
||||
<img src="' . $item['thumbnail'] . '">
|
||||
<br>Variation : ' . $item['version']
|
||||
. '<br>Prix : ' . $item['price']
|
||||
. '<br>Année : ' . $item['year']
|
||||
. '<br>Kilométrage : ' . $item['mileage']
|
||||
. '<br>Département : ' . $item['departement']
|
||||
. '<br>Type de vendeur : ' . $item['sellerType'];
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -356,6 +356,7 @@ class LeBonCoinBridge extends BridgeAbstract {
|
||||
$data = $this->buildRequestJson();
|
||||
|
||||
$header = array(
|
||||
'User-Agent: LBC;Android;Null;Null;Null;Null;Null;Null;Null;Null',
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data),
|
||||
'api_key: ' . self::$LBC_API_KEY
|
||||
|
89
bridges/MastodonBridge.php
Normal file
89
bridges/MastodonBridge.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
class MastodonBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'husim0';
|
||||
const NAME = 'Mastodon Bridge';
|
||||
const CACHE_TIMEOUT = 900; // 15mn
|
||||
const DESCRIPTION = 'Returns toots';
|
||||
const URI = 'https://mastodon.social';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'canusername' => array(
|
||||
'name' => 'Canonical username (ex : @sebsauvage@framapiaf.org)',
|
||||
'required' => true,
|
||||
),
|
||||
'norep' => array(
|
||||
'name' => 'Without replies',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Only return initial toots'
|
||||
),
|
||||
'noboost' => array(
|
||||
'name' => 'Without boosts',
|
||||
'required' => false,
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Hide boosts'
|
||||
)
|
||||
));
|
||||
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
return $this->getInput('canusername');
|
||||
default: return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
protected function parseItem($newItem){
|
||||
$item = parent::parseItem($newItem);
|
||||
|
||||
$content = str_get_html($item['content']);
|
||||
$title = str_get_html($item['title']);
|
||||
|
||||
$item['title'] = $content->plaintext;
|
||||
|
||||
if(strlen($item['title']) > 75) {
|
||||
$item['title'] = substr($item['title'], 0, strpos(wordwrap($item['title'], 75), "\n")) . '...';
|
||||
}
|
||||
|
||||
if(strpos($title, 'shared a status by') !== false) {
|
||||
if($this->getInput('noboost')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
preg_match('/shared a status by (\S{0,})/', $title, $matches);
|
||||
$item['title'] = 'Boost ' . $matches[1] . ' ' . $item['title'];
|
||||
$item['author'] = $matches[1];
|
||||
} else {
|
||||
$item['author'] = $this->getInput('canusername');
|
||||
}
|
||||
|
||||
// Check if it's a initial toot or a response
|
||||
if($this->getInput('norep') && preg_match('/^@.+/', trim($content->plaintext))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function getInstance(){
|
||||
preg_match('/^@[a-zA-Z0-9_]+@(.+)/', $this->getInput('canusername'), $matches);
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
private function getUsername(){
|
||||
preg_match('/^@([a-zA-Z_0-9_]+)@.+/', $this->getInput('canusername'), $matches);
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if($this->getInput('canusername'))
|
||||
return 'https://' . $this->getInstance() . '/users/' . $this->getUsername() . '.atom';
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
return $this->collectExpandableDatas($this->getURI());
|
||||
}
|
||||
}
|
60
bridges/MediapartBridge.php
Normal file
60
bridges/MediapartBridge.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class MediapartBridge extends FeedExpander {
|
||||
const MAINTAINER = 'killruana';
|
||||
const NAME = 'Mediapart Bridge';
|
||||
const URI = 'https://www.mediapart.fr/';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'single_page_mode' => array(
|
||||
'name' => 'Single page article',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Display long articles on a single page',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'mpsessid' => array(
|
||||
'name' => 'MPSESSID',
|
||||
'type' => 'text',
|
||||
'title' => 'Value of the session cookie MPSESSID'
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData() {
|
||||
$url = self::URI . 'articles/feed';
|
||||
$this->collectExpandableDatas($url);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem) {
|
||||
$item = parent::parseItem($newsItem);
|
||||
|
||||
// Enable single page mode?
|
||||
if ($this->getInput('single_page_mode') === true) {
|
||||
$item['uri'] .= '?onglet=full';
|
||||
}
|
||||
|
||||
// If a session cookie is defined, get the full article
|
||||
$mpsessid = $this->getInput('mpsessid');
|
||||
if (!empty($mpsessid)) {
|
||||
// Set the session cookie
|
||||
$opt = array();
|
||||
$opt[CURLOPT_COOKIE] = 'MPSESSID=' . $mpsessid;
|
||||
|
||||
// Get the page
|
||||
$articlePage = getSimpleHTMLDOM(
|
||||
$newsItem->link . '?onglet=full',
|
||||
array(),
|
||||
$opt);
|
||||
|
||||
// Extract the article content
|
||||
$content = $articlePage->find('div.content-article', 0)->innertext;
|
||||
$content = sanitize($content);
|
||||
$content = defaultLinkTo($content, static::URI);
|
||||
$item['content'] .= $content;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
153
bridges/MozillaBugTrackerBridge.php
Normal file
153
bridges/MozillaBugTrackerBridge.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
class MozillaBugTrackerBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Mozilla Bug Tracker';
|
||||
const URI = 'https://bugzilla.mozilla.org';
|
||||
const DESCRIPTION = 'Returns feeds for bug comments';
|
||||
const MAINTAINER = 'AntoineTurmel';
|
||||
const PARAMETERS = array(
|
||||
'Bug comments' => array(
|
||||
'id' => array(
|
||||
'name' => 'Bug tracking ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'title' => 'Insert bug tracking ID',
|
||||
'exampleValue' => 121241
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Number of comments to return',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify number of comments to return',
|
||||
'defaultValue' => -1
|
||||
),
|
||||
'sorting' => array(
|
||||
'name' => 'Sorting',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'title' => 'Defines the sorting order of the comments returned',
|
||||
'defaultValue' => 'of',
|
||||
'values' => array(
|
||||
'Oldest first' => 'of',
|
||||
'Latest first' => 'lf'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $bugid = '';
|
||||
private $bugdesc = '';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/extensions/BMO/web/images/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$limit = $this->getInput('limit');
|
||||
$sorting = $this->getInput('sorting');
|
||||
|
||||
// We use the print preview page for simplicity
|
||||
$html = getSimpleHTMLDOMCached($this->getURI() . '&format=multiple',
|
||||
86400,
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
DEFAULT_TARGET_CHARSET,
|
||||
false, // Do NOT remove line breaks
|
||||
DEFAULT_BR_TEXT,
|
||||
DEFAULT_SPAN_TEXT);
|
||||
|
||||
if($html === false)
|
||||
returnServerError('Failed to load page!');
|
||||
|
||||
// Store header information into private members
|
||||
$this->bugid = $html->find('#bugzilla-body', 0)->find('a', 0)->innertext;
|
||||
$this->bugdesc = $html->find('table.bugfields', 0)->find('tr', 0)->find('td', 0)->innertext;
|
||||
|
||||
// Get and limit comments
|
||||
$comments = $html->find('.bz_comment_table div.bz_comment');
|
||||
|
||||
if($limit > 0 && count($comments) > $limit) {
|
||||
$comments = array_slice($comments, count($comments) - $limit, $limit);
|
||||
}
|
||||
|
||||
// Order comments
|
||||
switch($sorting) {
|
||||
case 'lf': $comments = array_reverse($comments, true);
|
||||
case 'of':
|
||||
default: // Nothing to do, keep original order
|
||||
}
|
||||
|
||||
foreach($comments as $comment) {
|
||||
$comment = $this->inlineStyles($comment);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '#' . $comment->id;
|
||||
$item['author'] = $comment->find('span.bz_comment_user', 0)->innertext;
|
||||
$item['title'] = $comment->find('span.bz_comment_number', 0)->find('a', 0)->innertext;
|
||||
$item['timestamp'] = strtotime($comment->find('span.bz_comment_time', 0)->innertext);
|
||||
$item['content'] = $comment->find('pre.bz_comment_text', 0)->innertext;
|
||||
|
||||
// Fix line breaks (they use LF)
|
||||
$item['content'] = str_replace("\n", '<br>', $item['content']);
|
||||
|
||||
// Fix relative URIs
|
||||
$item['content'] = $this->replaceRelativeURI($item['content']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Bug comments':
|
||||
return parent::getURI()
|
||||
. '/show_bug.cgi?id='
|
||||
. $this->getInput('id');
|
||||
break;
|
||||
default: return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Bug comments':
|
||||
return 'Bug '
|
||||
. $this->bugid
|
||||
. ' tracker for '
|
||||
. $this->bugdesc
|
||||
. ' - '
|
||||
. parent::getName();
|
||||
break;
|
||||
default: return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all relative URIs with absolute ones
|
||||
*
|
||||
* @param string $content The source string
|
||||
* @return string Returns the source string with all relative URIs replaced
|
||||
* by absolute ones.
|
||||
*/
|
||||
private function replaceRelativeURI($content){
|
||||
return preg_replace('/href="(?!http)/', 'href="' . self::URI . '/', $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds styles as attributes to tags with known classes
|
||||
*
|
||||
* @param object $html A simplehtmldom object
|
||||
* @return object Returns the original object with styles added as
|
||||
* attributes.
|
||||
*/
|
||||
private function inlineStyles($html){
|
||||
foreach($html->find('.bz_obsolete') as $element) {
|
||||
$element->style = 'text-decoration:line-through;';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
26
bridges/NYTBridge.php
Normal file
26
bridges/NYTBridge.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
class NYTBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'IceWreck';
|
||||
const NAME = 'New York Times Bridge';
|
||||
const URI = 'https://www.nytimes.com/';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'RSS feed for the New York Times';
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('https://rss.nytimes.com/services/xml/rss/nyt/HomePage.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);
|
||||
// p > css-exrw3m has the actual article
|
||||
foreach($articlePage->find('p.css-exrw3m') as $element)
|
||||
$article = $article . $element;
|
||||
$item['content'] = $article;
|
||||
return $item;
|
||||
}
|
||||
}
|
194
bridges/NationalGeographicBridge.php
Normal file
194
bridges/NationalGeographicBridge.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
class NationalGeographicBridge extends BridgeAbstract {
|
||||
|
||||
const CONTEXT_BY_TOPIC = 'By Topic';
|
||||
const PARAMETER_TOPIC = 'topic';
|
||||
const PARAMETER_FULL_ARTICLE = 'full';
|
||||
const TOPIC_MAGAZINE = 'Magazine';
|
||||
const TOPIC_LATEST_STORIES = 'Latest Stories';
|
||||
|
||||
const NAME = 'National Geographic';
|
||||
const URI = 'https://www.nationalgeographic.com/';
|
||||
const DESCRIPTION = 'Fetches the latest articles from the National Geographic Magazine';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const PARAMETERS = array(
|
||||
self::CONTEXT_BY_TOPIC => array(
|
||||
self::PARAMETER_TOPIC => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
self::TOPIC_MAGAZINE => 'magazine',
|
||||
self::TOPIC_LATEST_STORIES => 'latest-stories'
|
||||
),
|
||||
'title' => 'Select your topic',
|
||||
'defaultValue' => 'Magazine'
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
self::PARAMETER_FULL_ARTICLE => array(
|
||||
'name' => 'Full Article',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Enable to load full articles (takes longer)'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $topicName = '';
|
||||
|
||||
public function getURI() {
|
||||
switch ($this->queriedContext) {
|
||||
case self::CONTEXT_BY_TOPIC: {
|
||||
return self::URI . $this->getInput(self::PARAMETER_TOPIC);
|
||||
} break;
|
||||
default: {
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->topicName = $this->getTopicName($this->getInput(self::PARAMETER_TOPIC));
|
||||
|
||||
switch($this->topicName) {
|
||||
case self::TOPIC_MAGAZINE: {
|
||||
return $this->collectMagazine();
|
||||
} break;
|
||||
case self::TOPIC_LATEST_STORIES: {
|
||||
return $this->collectLatestStories();
|
||||
} break;
|
||||
default: {
|
||||
returnServerError('Unknown topic: "' . $this->topicName . '"');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch ($this->queriedContext) {
|
||||
case self::CONTEXT_BY_TOPIC: {
|
||||
return static::NAME . ': ' . $this->topicName;
|
||||
} break;
|
||||
default: {
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getTopicName($topic) {
|
||||
return array_search($topic, static::PARAMETERS[self::CONTEXT_BY_TOPIC][self::PARAMETER_TOPIC]['values']);
|
||||
}
|
||||
|
||||
private function collectMagazine() {
|
||||
$uri = $this->getURI();
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request ' . $uri);
|
||||
|
||||
$script = $html->find('#lead-component script')[0];
|
||||
|
||||
$json = json_decode($script->innertext, true);
|
||||
|
||||
// This is probably going to break in the future, fix it then :)
|
||||
foreach($json['body']['0']['multilayout_promo_beta']['stories'] as $story) {
|
||||
$this->addStory($story);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectLatestStories() {
|
||||
$uri = self::URI . 'latest-stories/_jcr_content/content/hubfeed.promo-hub-feed-all-stories.json';
|
||||
|
||||
$json_raw = getContents($uri)
|
||||
or returnServerError('Could not request ' . $uri);
|
||||
|
||||
foreach(json_decode($json_raw, true) as $story) {
|
||||
$this->addStory($story);
|
||||
}
|
||||
}
|
||||
|
||||
private function addStory($story) {
|
||||
$title = 'Unknown title';
|
||||
$content = '';
|
||||
|
||||
foreach($story['components'] as $component) {
|
||||
switch($component['content_type']) {
|
||||
case 'title': {
|
||||
$title = $component['title']['text'];
|
||||
} break;
|
||||
case 'dek': {
|
||||
$content = $component['dek']['text'];
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $story['uri'];
|
||||
$item['title'] = $title;
|
||||
|
||||
// if full article is requested!
|
||||
if ($this->getInput(self::PARAMETER_FULL_ARTICLE))
|
||||
$item['content'] = $this->getFullArticle($item['uri']);
|
||||
else
|
||||
$item['content'] = $content;
|
||||
|
||||
if (isset($story['promo_image'])) {
|
||||
switch($story['promo_image']['content_type']) {
|
||||
case 'image': {
|
||||
$item['enclosures'][] = $story['promo_image']['image']['uri'];
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($story['lead_media'])) {
|
||||
$media = $story['lead_media'];
|
||||
switch($media['content_type']) {
|
||||
case 'image': {
|
||||
// Don't add if promo_image was added
|
||||
if (empty($item['enclosures']))
|
||||
$item['enclosures'][] = $media['image']['uri'];
|
||||
} break;
|
||||
case 'image_gallery': {
|
||||
foreach($media['image_gallery']['images'] as $image) {
|
||||
$item['enclosures'][] = $image['uri'];
|
||||
}
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function getFullArticle($uri) {
|
||||
$html = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Could not load ' . $uri);
|
||||
|
||||
$html = defaultLinkTo($html, $uri);
|
||||
|
||||
$content = '';
|
||||
|
||||
foreach($html->find('
|
||||
.content > .smartbody.text,
|
||||
.content > .section.image script[type="text/json"],
|
||||
.content > .section.image span[itemprop="caption"],
|
||||
.content > .section.inline script[type="text/json"]
|
||||
') as $element) {
|
||||
if ($element->tag === 'script') {
|
||||
$json = json_decode($element->innertext, true);
|
||||
if (isset($json['src'])) {
|
||||
$content .= '<img src="' . $json['src'] . '" width="100%" alt="' . $json['alt'] . '">';
|
||||
} elseif (isset($json['galleryType']) && isset($json['endpoint'])) {
|
||||
$doc = getContents($json['endpoint'])
|
||||
or returnServerError('Could not load ' . $json['endpoint']);
|
||||
$json = json_decode($doc, true);
|
||||
foreach($json['items'] as $item) {
|
||||
$content .= '<p>' . $item['caption'] . '</p>';
|
||||
$content .= '<img src="' . $item['url'] . '" width="100%" alt="' . $item['caption'] . '">';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$content .= $element->outertext;
|
||||
}
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ class NovelUpdatesBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'albirew';
|
||||
const NAME = 'Novel Updates';
|
||||
const URI = 'http://www.novelupdates.com/';
|
||||
const URI = 'https://www.novelupdates.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns releases from Novel Updates';
|
||||
const PARAMETERS = array( array(
|
||||
|
203
bridges/PatreonBridge.php
Normal file
203
bridges/PatreonBridge.php
Normal file
@@ -0,0 +1,203 @@
|
||||
<?php
|
||||
class PatreonBridge extends BridgeAbstract {
|
||||
const NAME = 'Patreon Bridge';
|
||||
const URI = 'https://www.patreon.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Returns posts by creators on Patreon';
|
||||
const MAINTAINER = 'Roliga';
|
||||
const PARAMETERS = array( array(
|
||||
'creator' => array(
|
||||
'name' => 'Creator',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Creator name as seen in their page URL'
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 86400)
|
||||
or returnServerError('Failed to load creator page at ' . $this->getURI());
|
||||
$regex = '#/api/campaigns/([0-9]+)#';
|
||||
if(preg_match($regex, $html->save(), $matches) > 0) {
|
||||
$campaign_id = $matches[1];
|
||||
} else {
|
||||
returnServerError('Could not find campaign ID');
|
||||
}
|
||||
|
||||
$query = array(
|
||||
'include' => implode(',', array(
|
||||
'user',
|
||||
'attachments',
|
||||
'user_defined_tags',
|
||||
//'campaign',
|
||||
//'poll.choices',
|
||||
//'poll.current_user_responses.user',
|
||||
//'poll.current_user_responses.choice',
|
||||
//'poll.current_user_responses.poll',
|
||||
//'access_rules.tier.null',
|
||||
//'images.null',
|
||||
//'audio.null'
|
||||
)),
|
||||
'fields' => array(
|
||||
'post' => implode(',', array(
|
||||
//'change_visibility_at',
|
||||
//'comment_count',
|
||||
'content',
|
||||
//'current_user_can_delete',
|
||||
//'current_user_can_view',
|
||||
//'current_user_has_liked',
|
||||
//'embed',
|
||||
'image',
|
||||
//'is_paid',
|
||||
//'like_count',
|
||||
//'min_cents_pledged_to_view',
|
||||
//'patreon_url',
|
||||
//'patron_count',
|
||||
//'pledge_url',
|
||||
//'post_file',
|
||||
//'post_metadata',
|
||||
//'post_type',
|
||||
'published_at',
|
||||
'teaser_text',
|
||||
//'thumbnail_url',
|
||||
'title',
|
||||
//'upgrade_url',
|
||||
'url',
|
||||
//'was_posted_by_campaign_owner'
|
||||
)),
|
||||
'user' => implode(',', array(
|
||||
//'image_url',
|
||||
'full_name',
|
||||
//'url'
|
||||
))
|
||||
),
|
||||
'filter' => array(
|
||||
'contains_exclusive_posts' => true,
|
||||
'is_draft' => false,
|
||||
'campaign_id' => $campaign_id
|
||||
),
|
||||
'sort' => '-published_at'
|
||||
);
|
||||
$posts = $this->apiGet('posts', $query);
|
||||
|
||||
foreach($posts->data as $post) {
|
||||
$item = array(
|
||||
'uri' => $post->attributes->url,
|
||||
'title' => $post->attributes->title,
|
||||
'timestamp' => $post->attributes->published_at,
|
||||
'content' => '',
|
||||
'uid' => 'patreon.com/' . $post->id
|
||||
);
|
||||
|
||||
$user = $this->findInclude($posts,
|
||||
'user',
|
||||
$post->relationships->user->data->id);
|
||||
$item['author'] = $user->full_name;
|
||||
|
||||
if(isset($post->attributes->image))
|
||||
$item['content'] .= '<p><a href="'
|
||||
. $post->attributes->url
|
||||
. '"><img src="'
|
||||
. $post->attributes->image->thumb_url
|
||||
. '" /></a></p>';
|
||||
|
||||
if(isset($post->attributes->content)) {
|
||||
$item['content'] .= $post->attributes->content;
|
||||
} elseif (isset($post->attributes->teaser_text)) {
|
||||
$item['content'] .= '<p>'
|
||||
. $post->attributes->teaser_text
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
if(isset($post->relationships->user_defined_tags)) {
|
||||
$item['categories'] = array();
|
||||
foreach($post->relationships->user_defined_tags->data as $tag) {
|
||||
$attrs = $this->findInclude($posts, 'post_tag', $tag->id);
|
||||
$item['categories'][] = $attrs->value;
|
||||
}
|
||||
}
|
||||
|
||||
if(isset($post->relationships->attachments)) {
|
||||
$item['enclosures'] = array();
|
||||
foreach($post->relationships->attachments->data as $attachment) {
|
||||
$attrs = $this->findInclude($posts, 'attachment', $attachment->id);
|
||||
$item['enclosures'][] = $attrs->url;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Searches the "included" array in an API response and returns attributes
|
||||
* for the first match.
|
||||
*/
|
||||
private function findInclude($data, $type, $id) {
|
||||
foreach($data->included as $include)
|
||||
if($include->type === $type && $include->id === $id)
|
||||
return $include->attributes;
|
||||
}
|
||||
|
||||
private function apiGet($endpoint, $query_data = array()) {
|
||||
$query_data['json-api-version'] = 1.0;
|
||||
$query_data['json-api-use-default-includes'] = 0;
|
||||
|
||||
$url = 'https://www.patreon.com/api/'
|
||||
. $endpoint
|
||||
. '?'
|
||||
. http_build_query($query_data);
|
||||
|
||||
/*
|
||||
* Accept-Language header and the CURL cipher list are for bypassing the
|
||||
* Cloudflare anti-bot protection on the Patreon API. If this ever breaks,
|
||||
* here are some other project that also deal with this:
|
||||
* https://github.com/mikf/gallery-dl/issues/342
|
||||
* https://github.com/daemionfox/patreon-feed/issues/7
|
||||
* https://www.patreondevelopers.com/t/api-returning-cloudflare-challenge/2025
|
||||
* https://github.com/splitbrain/patreon-rss/issues/4
|
||||
*/
|
||||
$header = array(
|
||||
'Accept-Language: en-US',
|
||||
'Content-Type: application/json'
|
||||
);
|
||||
$opts = array(
|
||||
CURLOPT_SSL_CIPHER_LIST => implode(':', array(
|
||||
'DEFAULT',
|
||||
'!DHE-RSA-CHACHA20-POLY1305'
|
||||
))
|
||||
);
|
||||
|
||||
$data = json_decode(getContents($url, $header, $opts))
|
||||
or returnServerError('API request to "' . $url . '" failed.');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('creator')))
|
||||
return $this->getInput('creator') . ' posts';
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('creator')))
|
||||
return self::URI . $this->getInput('creator');
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function detectParameters($url){
|
||||
$params = array();
|
||||
|
||||
// Matches e.g. https://www.patreon.com/SomeCreator
|
||||
$regex = '/^(https?:\/\/)?(www\.)?patreon\.com\/([^\/&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['creator'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -6,6 +6,16 @@ class PikabuBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'Выводит посты по тегу';
|
||||
const MAINTAINER = 'em92';
|
||||
|
||||
const PARAMETERS_FILTER = array(
|
||||
'name' => 'Фильтр',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Горячее' => 'hot',
|
||||
'Свежее' => 'new',
|
||||
),
|
||||
'defaultValue' => 'hot'
|
||||
);
|
||||
|
||||
const PARAMETERS = array(
|
||||
'По тегу' => array(
|
||||
'tag' => array(
|
||||
@@ -13,21 +23,38 @@ class PikabuBridge extends BridgeAbstract {
|
||||
'exampleValue' => 'it',
|
||||
'required' => true
|
||||
),
|
||||
'filter' => array(
|
||||
'name' => 'Фильтр',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Горячее' => 'hot',
|
||||
'Свежее' => 'new',
|
||||
),
|
||||
'defaultValue' => 'hot'
|
||||
'filter' => self::PARAMETERS_FILTER
|
||||
),
|
||||
'По сообществу' => array(
|
||||
'community' => array(
|
||||
'name' => 'Сообщество',
|
||||
'exampleValue' => 'linux',
|
||||
'required' => true
|
||||
),
|
||||
'filter' => self::PARAMETERS_FILTER
|
||||
),
|
||||
'По пользователю' => array(
|
||||
'user' => array(
|
||||
'name' => 'Пользователь',
|
||||
'exampleValue' => 'admin',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected $title = null;
|
||||
|
||||
public function getURI() {
|
||||
if ($this->getInput('tag')) {
|
||||
return self::URI . '/tag/' . rawurlencode($this->getInput('tag')) . '/' . rawurlencode($this->getInput('filter'));
|
||||
} else if ($this->getInput('user')) {
|
||||
return self::URI . '/@' . rawurlencode($this->getInput('user'));
|
||||
} else if ($this->getInput('community')) {
|
||||
$uri = self::URI . '/community/' . rawurlencode($this->getInput('community'));
|
||||
if ($this->getInput('filter') != 'hot') {
|
||||
$uri .= '/' . rawurlencode($this->getInput('filter'));
|
||||
}
|
||||
return $uri;
|
||||
} else {
|
||||
return parent::getURI();
|
||||
}
|
||||
@@ -38,10 +65,10 @@ class PikabuBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if (is_string($this->getInput('tag'))) {
|
||||
return $this->getInput('tag') . ' - ' . parent::getName();
|
||||
} else {
|
||||
if (is_null($this->title)) {
|
||||
return parent::getName();
|
||||
} else {
|
||||
return $this->title . ' - ' . parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,6 +79,8 @@ class PikabuBridge extends BridgeAbstract {
|
||||
$text_html = iconv('windows-1251', 'utf-8', $text_html);
|
||||
$html = str_get_html($text_html);
|
||||
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
|
||||
foreach($html->find('article.story') as $post) {
|
||||
$time = $post->find('time.story__datetime', 0);
|
||||
if (is_null($time)) continue;
|
||||
@@ -67,6 +96,11 @@ class PikabuBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
foreach($post->find('[data-type=gifx]') as $el) {
|
||||
$src = $el->getAttribute('data-source');
|
||||
$el->outertext = '<img src="' . $src . '">';
|
||||
}
|
||||
|
||||
foreach($post->find('img') as $img) {
|
||||
$src = $img->getAttribute('src');
|
||||
if (!$src) {
|
||||
|
@@ -16,12 +16,6 @@ class PinterestBridge extends FeedExpander {
|
||||
'name' => 'board',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'From search' => array(
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -29,17 +23,9 @@ class PinterestBridge extends FeedExpander {
|
||||
return 'https://s.pinimg.com/webapp/style/images/favicon-9f8f9adf.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username and board':
|
||||
$this->collectExpandableDatas($this->getURI() . '.rss');
|
||||
$this->fixLowRes();
|
||||
break;
|
||||
case 'From search':
|
||||
default:
|
||||
$html = getSimpleHTMLDOMCached($this->getURI());
|
||||
$this->getSearchResults($html);
|
||||
}
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas($this->getURI() . '.rss');
|
||||
$this->fixLowRes();
|
||||
}
|
||||
|
||||
private function fixLowRes() {
|
||||
@@ -55,71 +41,21 @@ class PinterestBridge extends FeedExpander {
|
||||
|
||||
}
|
||||
|
||||
private function getSearchResults($html){
|
||||
$json = json_decode($html->find('#jsInit1', 0)->innertext, true);
|
||||
$results = $json['resourceDataCache'][0]['data']['results'];
|
||||
public function getURI() {
|
||||
|
||||
foreach($results as $result) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = self::URI . $result['board']['url'];
|
||||
|
||||
// Some use regular titles, others provide 'advanced' infos, a few
|
||||
// provide even less info. Thus we attempt multiple options.
|
||||
$item['title'] = trim($result['title']);
|
||||
|
||||
if($item['title'] === '')
|
||||
$item['title'] = trim($result['rich_summary']['display_name']);
|
||||
|
||||
if($item['title'] === '')
|
||||
$item['title'] = trim($result['grid_description']);
|
||||
|
||||
$item['timestamp'] = strtotime($result['created_at']);
|
||||
$item['username'] = $result['pinner']['username'];
|
||||
$item['fullname'] = $result['pinner']['full_name'];
|
||||
$item['avatar'] = $result['pinner']['image_small_url'];
|
||||
$item['author'] = $item['username'] . ' (' . $item['fullname'] . ')';
|
||||
$item['content'] = '<img align="left" style="margin: 2px 4px;" src="'
|
||||
. htmlentities($item['avatar'])
|
||||
. '" /><p><strong>'
|
||||
. $item['username']
|
||||
. '</strong><br>'
|
||||
. $item['fullname']
|
||||
. '</p><br><img src="'
|
||||
. $result['images']['736x']['url']
|
||||
. '" alt="" /><br><p>'
|
||||
. $result['description']
|
||||
. '</p>';
|
||||
|
||||
$item['enclosures'] = array($result['images']['orig']['url']);
|
||||
|
||||
$this->items[] = $item;
|
||||
if ($this->queriedContext === 'By username and board') {
|
||||
return self::URI . '/' . urlencode($this->getInput('u')) . '/' . urlencode($this->getInput('b'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username and board':
|
||||
$uri = self::URI . '/' . urlencode($this->getInput('u')) . '/' . urlencode($this->getInput('b'));// . '.rss';
|
||||
break;
|
||||
case 'From search':
|
||||
$uri = self::URI . '/search/?q=' . urlencode($this->getInput('q'));
|
||||
break;
|
||||
default: return parent::getURI();
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
public function getName() {
|
||||
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By username and board':
|
||||
$specific = $this->getInput('u') . ' - ' . $this->getInput('b');
|
||||
break;
|
||||
case 'From search':
|
||||
$specific = $this->getInput('q');
|
||||
break;
|
||||
default: return parent::getName();
|
||||
if ($this->queriedContext === 'By username and board') {
|
||||
return $this->getInput('u') . ' - ' . $this->getInput('b') . ' - ' . self::NAME;
|
||||
}
|
||||
return $specific . ' - ' . self::NAME;
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
88
bridges/PirateCommunityBridge.php
Normal file
88
bridges/PirateCommunityBridge.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
class PirateCommunityBridge extends BridgeAbstract {
|
||||
const NAME = 'Pirate-Community Bridge';
|
||||
const URI = 'https://raymanpc.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Returns replies to topics';
|
||||
const MAINTAINER = 'Roliga';
|
||||
const PARAMETERS = array( array(
|
||||
't' => array(
|
||||
'name' => 'Topic ID',
|
||||
'type' => 'number',
|
||||
'title' => 'Topic ID from topic URL. If the URL contains t=12 the ID is 12.',
|
||||
'required' => true
|
||||
)));
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function detectParameters($url){
|
||||
$parsed_url = parse_url($url);
|
||||
|
||||
if($parsed_url['host'] !== 'raymanpc.com')
|
||||
return null;
|
||||
|
||||
parse_str($parsed_url['query'], $parsed_query);
|
||||
|
||||
if($parsed_url['path'] === '/forum/viewtopic.php'
|
||||
&& array_key_exists('t', $parsed_query)) {
|
||||
return array('t' => $parsed_query['t']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if(!empty($this->feedName))
|
||||
return $this->feedName;
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('t'))) {
|
||||
return self::URI
|
||||
. 'forum/viewtopic.php?t='
|
||||
. $this->getInput('t')
|
||||
. '&sd=d'; // sort posts decending by ate so first page has latest posts
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not retrieve topic page at ' . $this->getURI());
|
||||
|
||||
$this->feedName = $html->find('head title', 0)->plaintext;
|
||||
|
||||
foreach($html->find('.post') as $reply) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI()
|
||||
. $reply->find('h3 a', 0)->getAttribute('href');
|
||||
|
||||
$item['title'] = $reply->find('h3 a', 0)->plaintext;
|
||||
|
||||
$author_html = $reply->find('.author', 0);
|
||||
// author_html contains the timestamp as text directly inside it,
|
||||
// so delete all other child elements
|
||||
foreach($author_html->children as $child)
|
||||
$child->outertext = '';
|
||||
// Timestamps are always in UTC+1
|
||||
$item['timestamp'] = trim($author_html->innertext) . ' +01:00';
|
||||
|
||||
$item['author'] = $reply
|
||||
->find('.username, .username-coloured', 0)
|
||||
->plaintext;
|
||||
|
||||
$item['content'] = defaultLinkTo($reply->find('.content', 0)->innertext,
|
||||
$this->getURI());
|
||||
|
||||
$item['enclosures'] = array();
|
||||
foreach($reply->find('.attachbox img.postimage') as $img)
|
||||
$item['enclosures'][] = urljoin($this->getURI(), $img->src);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
132
bridges/QPlayBridge.php
Normal file
132
bridges/QPlayBridge.php
Normal file
@@ -0,0 +1,132 @@
|
||||
<?php
|
||||
class QPlayBridge extends BridgeAbstract {
|
||||
const NAME = 'Q Play';
|
||||
const URI = 'https://www.qplay.pt';
|
||||
const DESCRIPTION = 'Entretenimento e humor em Português';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
'Program' => array(
|
||||
'program' => array(
|
||||
'name' => 'Program Name',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
'Catalog' => array(
|
||||
'all_pages' => array(
|
||||
'name' => 'All Pages',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
# This should be the favicon served on `self::URI`
|
||||
return 'https://s3.amazonaws.com/unode1/assets/4957/r3T9Lm9LTLmpAEX6FlSA_apple-touch-icon.png';
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch ($this->queriedContext) {
|
||||
case 'Program':
|
||||
return self::URI . '/programs/' . $this->getInput('program');
|
||||
case 'Catalog':
|
||||
return self::URI . '/catalog';
|
||||
}
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch ($this->queriedContext) {
|
||||
case 'Program':
|
||||
$html = getSimpleHTMLDOMCached($this->getURI())
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
return $html->find('h1.program--title', 0)->innertext;
|
||||
case 'Catalog':
|
||||
return self::NAME . ' | Programas';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
/* This uses the uscreen platform, other sites can adapt this. https://www.uscreen.tv/ */
|
||||
public function collectData() {
|
||||
switch ($this->queriedContext) {
|
||||
case 'Program':
|
||||
$program = $this->getInput('program');
|
||||
$html = getSimpleHTMLDOMCached($this->getURI())
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach($html->find('.cce--thumbnails-video-chapter') as $element) {
|
||||
$cid = $element->getAttribute('data-id');
|
||||
$item['title'] = $element->find('.cce--chapter-title', 0)->innertext;
|
||||
$item['content'] = $element->find('.cce--thumbnails-image-block', 0)
|
||||
. $element->find('.cce--chapter-body', 0)->innertext;
|
||||
$item['uri'] = $this->getURI() . '?cid=' . $cid;
|
||||
|
||||
/* TODO: Suport login credentials? */
|
||||
/* # Get direct video URL */
|
||||
/* $json_source = getContents(self::URI . '/chapters/' . $cid, array('Cookie: _uscreen2_session=???;')) */
|
||||
/* or returnServerError('Could not request chapter JSON'); */
|
||||
/* $json = json_decode($json_source); */
|
||||
|
||||
/* $item['enclosures'] = [$json->fallback]; */
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'Catalog':
|
||||
$json_raw = getContents($this->getCatalogURI(1))
|
||||
or returnServerError('Could not load catalog content');
|
||||
|
||||
$json = json_decode($json_raw);
|
||||
$total_pages = $json->total_pages;
|
||||
|
||||
foreach($this->parseCatalogPage($json) as $item) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
if ($this->getInput('all_pages') === true) {
|
||||
foreach(range(2, $total_pages) as $page) {
|
||||
$json_raw = getContents($this->getCatalogURI($page))
|
||||
or returnServerError('Could not load catalog content (all pages)');
|
||||
|
||||
$json = json_decode($json_raw);
|
||||
|
||||
foreach($this->parseCatalogPage($json) as $item) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCatalogURI($page) {
|
||||
return self::URI . '/catalog.json?page=' . $page;
|
||||
}
|
||||
|
||||
private function parseCatalogPage($json) {
|
||||
$items = array();
|
||||
|
||||
foreach($json->records as $record) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $record->title;
|
||||
$item['content'] = $record->description
|
||||
. '<div>Duration: ' . $record->duration . '</div>';
|
||||
$item['timestamp'] = strtotime($record->release_date);
|
||||
$item['uri'] = self::URI . $record->url;
|
||||
$item['enclosures'] = array(
|
||||
$record->main_poster,
|
||||
);
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
}
|
@@ -1,34 +1,87 @@
|
||||
<?php
|
||||
class RadioMelodieBridge extends BridgeAbstract {
|
||||
const NAME = 'Radio Melodie Actu';
|
||||
const URI = 'https://www.radiomelodie.com/';
|
||||
const URI = 'https://www.radiomelodie.com';
|
||||
const DESCRIPTION = 'Retourne les actualités publiées par Radio Melodie';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'img/favicon.png';
|
||||
return self::URI . '/img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'actu')
|
||||
$html = getSimpleHTMLDOM(self::URI . '/actu/')
|
||||
or returnServerError('Could not request Radio Melodie.');
|
||||
$list = $html->find('div[class=actuitem]');
|
||||
$list = $html->find('div[class=displayList]', 0)->children();
|
||||
foreach($list as $element) {
|
||||
$item = array();
|
||||
if($element->tag == 'a') {
|
||||
$articleURL = self::URI . $element->href;
|
||||
$article = getSimpleHTMLDOM($articleURL);
|
||||
$textDOM = $article->find('article', 0);
|
||||
|
||||
// Get picture URL
|
||||
$pictureHTML = $element->find('div[class=picture]');
|
||||
preg_match(
|
||||
'/background-image:url\((.*)\);/',
|
||||
$pictureHTML[0]->getAttribute('style'),
|
||||
$pictures);
|
||||
$pictureURL = $pictures[1];
|
||||
// Initialise arrays
|
||||
$item = array();
|
||||
$audio = array();
|
||||
$picture = array();
|
||||
|
||||
$item['enclosures'] = array($pictureURL);
|
||||
$item['uri'] = self::URI . $element->parent()->href;
|
||||
$item['title'] = $element->find('h3', 0)->plaintext;
|
||||
$item['content'] = $element->find('p', 0)->plaintext . '<br/><img src="' . $pictureURL . '"/>';
|
||||
$this->items[] = $item;
|
||||
// Get the Main picture URL
|
||||
$picture[] = $this->rewriteImage($article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src);
|
||||
$audioHTML = $article->find('audio');
|
||||
|
||||
// Add the audio element to the enclosure
|
||||
foreach($audioHTML as $audioElement) {
|
||||
$audioURL = $audioElement->src;
|
||||
$audio[] = $audioURL;
|
||||
}
|
||||
|
||||
// Rewrite pictures URL
|
||||
$imgs = $textDOM->find('img[src^="http://www.radiomelodie.com/image.php]');
|
||||
foreach($imgs as $img) {
|
||||
$img->src = $this->rewriteImage($img->src);
|
||||
$article->save();
|
||||
}
|
||||
|
||||
// Remove Google Ads
|
||||
$ads = $article->find('div[class=adInline]');
|
||||
foreach($ads as $ad) {
|
||||
$ad->outertext = '';
|
||||
$article->save();
|
||||
}
|
||||
|
||||
// Remove Radio Melodie Logo
|
||||
$logoHTML = $article->find('div[id=logoArticleRM]', 0);
|
||||
$logoHTML->outertext = '';
|
||||
$article->save();
|
||||
|
||||
$author = $article->find('p[class=AuthorName]', 0)->plaintext;
|
||||
|
||||
$item['enclosures'] = array_merge($picture, $audio);
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $articleURL;
|
||||
$item['title'] = $article->find('meta[property=og:title]', 0)->content;
|
||||
$date = $article->find('p[class*=date]', 0)->plaintext;
|
||||
|
||||
// Header Image
|
||||
$header = '<img src="' . $picture[0] . '"/>';
|
||||
|
||||
// Remove the Date and Author part
|
||||
$textDOM->find('div[class=AuthorDate]', 0)->outertext = '';
|
||||
$article->save();
|
||||
$text = $textDOM->innertext;
|
||||
$item['content'] = '<h1>' . $item['title'] . '</h1>' . $date . '<br/>' . $header . $text;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Function to rewrite image URL to use the real Image URL and not the resized one (which is very slow)
|
||||
*/
|
||||
private function rewriteImage($url)
|
||||
{
|
||||
$parts = explode('?', $url);
|
||||
parse_str(html_entity_decode($parts[1]), $params);
|
||||
return self::URI . '/' . $params['image'];
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -6,98 +6,60 @@ class RoadAndTrackBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 86400; // 24h
|
||||
const DESCRIPTION = 'Returns the latest news from Road & Track.';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'new-cars' => array(
|
||||
'name' => 'New Cars',
|
||||
'type' => 'checkbox',
|
||||
'exampleValue' => 'checked',
|
||||
'title' => 'Activate to load New Cars articles'
|
||||
),
|
||||
'motorsports' => array(
|
||||
'name' => 'Motorsports',
|
||||
'type' => 'checkbox',
|
||||
'exampleValue' => 'checked',
|
||||
'title' => 'Activate to load Motorsports articles'
|
||||
),
|
||||
'car-culture' => array(
|
||||
'name' => 'Car Culture',
|
||||
'type' => 'checkbox',
|
||||
'exampleValue' => 'checked',
|
||||
'title' => 'Activate to load Car Culture articles'
|
||||
),
|
||||
'car-shows' => array(
|
||||
'name' => 'Car shows',
|
||||
'type' => 'checkbox',
|
||||
'exampleValue' => 'checked',
|
||||
'title' => 'Activate to load Car shows articles'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const SIG_URL = 'https://cloud.mazdigital.com/feeds/production/comboapp/204/api/v3/';
|
||||
|
||||
public function collectData() {
|
||||
|
||||
//Magic
|
||||
$signVal = '?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc291cmNlIjoiaHR0cHM6Ly9jbG91ZC5tYXpkaWd';
|
||||
$signVal .= 'pdGFsLmNvbS9mZWVkcy9wcm9kdWN0aW9uL2NvbWJvYXBwLzIwNC8qIiwiQ29uZGl0aW9uIj';
|
||||
$signVal .= 'p7IkRhdGVMZXNzVGhhbiI6eyJBV1M6RXBvY2hUaW1lIjoxNTUyNTU5MDUzfSwiSXBBZGRyZ';
|
||||
$signVal .= 'XNzIjp7IkFXUzpTb3VyY2VJcCI6IjAuMC4wLjAvMCJ9fX1dfQ__&Signature=jgS~Jccjs';
|
||||
$signVal .= 'lXMMywWesmwDpUbHvEmrADRP7iBRzT~OiP-O~zI-8TtQzqTP7GUrpB9~v69CvhO7-JVtw94';
|
||||
$signVal .= 'VC3N6lQrwsxTTIhpS57YGeV~MbZx~P653yUV7jb3jpJE2yUawfXnEkD-XzOIn8-caMo~14i';
|
||||
$signVal .= 'KuWV9KNDkTJaRgOMy0rrVpWqiuBjCu5s5B8Ylt2qwcpOvHjXSqG9IY5c7GUIXKsk8yXzGFi';
|
||||
$signVal .= 'yzy8hfuGgdx0n7fgl7c4-EoDgQaz~U76g0epejPxV5Csj16rCCfAqBU5kZJnACZ1vvOvRcV';
|
||||
$signVal .= 'Wiu8KUuUuCS04SPmJ73Y5XoY8~uXRScxZG1kAFTIAhT4nYVlg__&Key-Pair-Id=APKAIZB';
|
||||
$signVal .= 'QNNSW4WGIFP4Q';
|
||||
$page = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$newsElements = array();
|
||||
if($this->getInput('new-cars')) {
|
||||
$newsElements = array_merge($newsElements,
|
||||
json_decode(getContents(self::SIG_URL . '7591/item_feed' . $signVal))
|
||||
);
|
||||
}
|
||||
if($this->getInput('motorsports')) {
|
||||
$newsElements = array_merge($newsElements,
|
||||
json_decode(getContents(self::SIG_URL . '7590/item_feed' . $signVal))
|
||||
);
|
||||
}
|
||||
if($this->getInput('car-culture')) {
|
||||
$newsElements = array_merge($newsElements,
|
||||
json_decode(getContents(self::SIG_URL . '7588/item_feed' . $signVal))
|
||||
);
|
||||
}
|
||||
if($this->getInput('car-shows')) {
|
||||
$newsElements = array_merge($newsElements,
|
||||
json_decode(getContents(self::SIG_URL . '7589/item_feed' . $signVal))
|
||||
);
|
||||
}
|
||||
|
||||
usort($newsElements, function($a, $b) {
|
||||
return $b->published - $a->published;
|
||||
});
|
||||
//Process the first element
|
||||
$firstArticleLink = $page->find('.custom-promo-title', 0)->href;
|
||||
$this->items[] = $this->fetchArticle($firstArticleLink);
|
||||
|
||||
$limit = 19;
|
||||
foreach($newsElements as $element) {
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $element->sourceUrl;
|
||||
$item['timestamp'] = $element->published;
|
||||
$item['enclosures'] = array($element->cover->url);
|
||||
$item['title'] = $element->title;
|
||||
$item['content'] = $this->getArticleContent($element);
|
||||
$this->items[] = $item;
|
||||
|
||||
if($limit > 0) {
|
||||
$limit--;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
|
||||
foreach($page->find('.full-item-title') as $article) {
|
||||
$this->items[] = $this->fetchArticle($article->href);
|
||||
$limit -= 1;
|
||||
if($limit == 0) break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function fixImages($content) {
|
||||
|
||||
$enclosures = [];
|
||||
foreach($content->find('img') as $image) {
|
||||
$image->src = explode('?', $image->getAttribute('data-src'))[0];
|
||||
$enclosures[] = $image->src;
|
||||
}
|
||||
|
||||
foreach($content->find('.embed-image-wrap, .content-lede-image-wrap') as $imgContainer) {
|
||||
$imgContainer->style = '';
|
||||
}
|
||||
|
||||
return $enclosures;
|
||||
|
||||
}
|
||||
|
||||
private function fetchArticle($articleLink) {
|
||||
|
||||
$articleLink = self::URI . $articleLink;
|
||||
$article = getSimpleHTMLDOM($articleLink);
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $article->find('.content-hed', 0)->innertext;
|
||||
$item['author'] = $article->find('.byline-name', 0)->innertext;
|
||||
$item['timestamp'] = strtotime($article->find('.content-info-date', 0)->getAttribute('datetime'));
|
||||
|
||||
$content = $article->find('.content-container', 0);
|
||||
if($content->find('.content-rail', 0) !== null)
|
||||
$content->find('.content-rail', 0)->innertext = '';
|
||||
$enclosures = $this->fixImages($content);
|
||||
|
||||
$item['enclosures'] = $enclosures;
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
|
||||
}
|
||||
|
||||
private function getArticleContent($article) {
|
||||
|
||||
return getContents($article->contentUrl);
|
||||
|
@@ -9,7 +9,7 @@ class Rue89Bridge extends BridgeAbstract {
|
||||
public function collectData() {
|
||||
|
||||
$jsonArticles = getContents('https://appdata.nouvelobs.com/rue89/feed.json')
|
||||
or die('Unable to query Rue89 !');
|
||||
or returnServerError('Unable to query Rue89 !');
|
||||
$articles = json_decode($jsonArticles)->items;
|
||||
foreach($articles as $article) {
|
||||
$this->items[] = $this->getArticle($article);
|
||||
@@ -19,7 +19,8 @@ class Rue89Bridge extends BridgeAbstract {
|
||||
|
||||
private function getArticle($articleInfo) {
|
||||
|
||||
$articleJson = getContents($articleInfo->json_url) or die('Unable to get article !');
|
||||
$articleJson = getContents($articleInfo->json_url)
|
||||
or returnServerError('Unable to get article !');
|
||||
$article = json_decode($articleJson);
|
||||
$item = array();
|
||||
$item['title'] = $article->title;
|
||||
|
@@ -7,4 +7,21 @@ class Rule34pahealBridge extends Shimmie2Bridge {
|
||||
const NAME = 'Rule34paheal';
|
||||
const URI = 'http://rule34.paheal.net/';
|
||||
const DESCRIPTION = 'Returns images from given page';
|
||||
|
||||
protected function getItemFromElement($element){
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . $element->href;
|
||||
$item['id'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['timestamp'] = time();
|
||||
$thumbnailUri = $element->find('img', 0)->src;
|
||||
$item['tags'] = $element->getAttribute('data-tags');
|
||||
$item['title'] = $this->getName() . ' | ' . $item['id'];
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $thumbnailUri
|
||||
. '" /></a><br>Tags: '
|
||||
. $item['tags'];
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
63
bridges/SIMARBridge.php
Normal file
63
bridges/SIMARBridge.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
class SIMARBridge extends BridgeAbstract {
|
||||
const NAME = 'SIMAR';
|
||||
const URI = 'http://www.simar-louresodivelas.pt/';
|
||||
const DESCRIPTION = 'Verificar estado da rede SIMAR';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
'Público' => array(
|
||||
'interventions' => array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Incluir Intervenções?',
|
||||
'defaultValue' => 'checked',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::getURI())
|
||||
or returnServerError('Could not load content');
|
||||
$e_home = $html->find('#home', 0)
|
||||
or returnServerError('Invalid site structure');
|
||||
|
||||
foreach($e_home->find('span') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = 'Rotura: ' . $element->plaintext;
|
||||
$item['content'] = $element->innertext;
|
||||
$item['uid'] = 'urn:sha1:' . hash('sha1', $item['content']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
if ($this->getInput('interventions')) {
|
||||
$e_main1 = $html->find('#menu1', 0)
|
||||
or returnServerError('Invalid site structure');
|
||||
|
||||
foreach ($e_main1->find('a') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = 'Intervenção: ' . $element->plaintext;
|
||||
$item['uri'] = self::getURI() . $element->href;
|
||||
$item['content'] = $element->innertext;
|
||||
|
||||
/* Try to get the actual contents for this kind of item */
|
||||
$item_html = getSimpleHTMLDOMCached($item['uri']);
|
||||
if ($item_html) {
|
||||
$e_item = $item_html->find('.auto-style59', 0);
|
||||
foreach($e_item->find('p') as $paragraph) {
|
||||
/* Remove empty paragraphs */
|
||||
if (preg_match('/^(\W| )+$/', $paragraph->innertext) == 1) {
|
||||
$paragraph->outertext = '';
|
||||
}
|
||||
}
|
||||
if ($e_item) {
|
||||
$item['content'] = $e_item->innertext;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,11 +0,0 @@
|
||||
<?php
|
||||
require_once('MoebooruBridge.php');
|
||||
|
||||
class SakugabooruBridge extends MoebooruBridge {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Sakugabooru';
|
||||
const URI = 'http://sakuga.yshi.org/';
|
||||
const DESCRIPTION = 'Returns images from given page';
|
||||
|
||||
}
|
@@ -2,70 +2,152 @@
|
||||
class ShanaprojectBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const NAME = 'Shanaproject Bridge';
|
||||
const URI = 'http://www.shanaproject.com';
|
||||
const URI = 'https://www.shanaproject.com';
|
||||
const DESCRIPTION = 'Returns a list of anime from the current Season Anime List';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'min_episodes' => array(
|
||||
'name' => 'Minimum Episodes',
|
||||
'type' => 'number',
|
||||
'title' => 'Minimum number of episodes before including in feed',
|
||||
'defaultValue' => 0,
|
||||
),
|
||||
'min_total_episodes' => array(
|
||||
'name' => 'Minimum Total Episodes',
|
||||
'type' => 'number',
|
||||
'title' => 'Minimum total number of episodes before including in feed',
|
||||
'defaultValue' => 0,
|
||||
),
|
||||
'require_banner' => array(
|
||||
'name' => 'Require Banner',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Only include anime with custom banner image',
|
||||
'defaultValue' => false,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
private $uri;
|
||||
|
||||
public function getURI() {
|
||||
return isset($this->uri) ? $this->uri : parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = $this->loadSeasonAnimeList();
|
||||
|
||||
$animes = $html->find('div.header_display_box_info')
|
||||
or returnServerError('Could not find anime headers!');
|
||||
|
||||
$min_episodes = $this->getInput('min_episodes') ?: 0;
|
||||
$min_total_episodes = $this->getInput('min_total_episodes') ?: 0;
|
||||
|
||||
foreach($animes as $anime) {
|
||||
|
||||
list(
|
||||
$episodes_released,
|
||||
/* of */,
|
||||
$episodes_total
|
||||
) = explode(' ', $this->extractAnimeEpisodeInformation($anime));
|
||||
|
||||
// Skip if not enough episodes yet
|
||||
if ($episodes_released < $min_episodes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if too many episodes in total
|
||||
if ($episodes_total !== '?' && $episodes_total < $min_total_episodes) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Skip if https://static.shanaproject.com/no-art.jpg
|
||||
if ($this->getInput('require_banner')
|
||||
&& strpos($this->extractAnimeBackgroundImage($anime), 'no-art') !== false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->items[] = array(
|
||||
'title' => $this->extractAnimeTitle($anime),
|
||||
'author' => $this->extractAnimeAuthor($anime),
|
||||
'uri' => $this->extractAnimeUri($anime),
|
||||
'timestamp' => $this->extractAnimeTimestamp($anime),
|
||||
'content' => $this->buildAnimeContent($anime),
|
||||
);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Returns an html object for the Season Anime List (latest season)
|
||||
private function loadSeasonAnimeList(){
|
||||
// First we need to find the URI to the latest season from the
|
||||
// 'seasons' page searching for 'Season Anime List'
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/seasons');
|
||||
if(!$html)
|
||||
returnServerError('Could not load \'seasons\' page!');
|
||||
|
||||
$season = $html->find('div.follows_menu/a', 1);
|
||||
if(!$season)
|
||||
returnServerError('Could not find \'Season Anime List\'!');
|
||||
$html = getSimpleHTMLDOM(self::URI . '/seasons')
|
||||
or returnServerError('Could not load \'seasons\' page!');
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI() . $season->href);
|
||||
if(!$html)
|
||||
returnServerError(
|
||||
$html = defaultLinkTo($html, self::URI . '/seasons');
|
||||
|
||||
$season = $html->find('div.follows_menu > a', 1)
|
||||
or returnServerError('Could not find \'Season Anime List\'!');
|
||||
|
||||
$html = getSimpleHTMLDOM($season->href)
|
||||
or returnServerError(
|
||||
'Could not load \'Season Anime List\' from \''
|
||||
. $season->innertext
|
||||
. '\'!'
|
||||
);
|
||||
|
||||
$this->uri = $season->href;
|
||||
|
||||
$html = defaultLinkTo($html, $season->href);
|
||||
|
||||
return $html;
|
||||
|
||||
}
|
||||
|
||||
// Extracts the anime title
|
||||
private function extractAnimeTitle($anime){
|
||||
$title = $anime->find('a', 0);
|
||||
if(!$title)
|
||||
returnServerError('Could not find anime title!');
|
||||
$title = $anime->find('a', 0)
|
||||
or returnServerError('Could not find anime title!');
|
||||
return trim($title->innertext);
|
||||
}
|
||||
|
||||
// Extracts the anime URI
|
||||
private function extractAnimeUri($anime){
|
||||
$uri = $anime->find('a', 0);
|
||||
if(!$uri)
|
||||
returnServerError('Could not find anime URI!');
|
||||
return $this->getURI() . $uri->href;
|
||||
$uri = $anime->find('a', 0)
|
||||
or returnServerError('Could not find anime URI!');
|
||||
return $uri->href;
|
||||
}
|
||||
|
||||
// Extracts the anime release date (timestamp)
|
||||
private function extractAnimeTimestamp($anime){
|
||||
$timestamp = $anime->find('span.header_info_block', 1);
|
||||
if(!$timestamp)
|
||||
|
||||
if(!$timestamp) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return strtotime($timestamp->innertext);
|
||||
}
|
||||
|
||||
// Extracts the anime studio name (author)
|
||||
private function extractAnimeAuthor($anime){
|
||||
$author = $anime->find('span.header_info_block', 2);
|
||||
if(!$author)
|
||||
return; // Sometimes the studio is unknown, so leave empty
|
||||
|
||||
if(!$author) {
|
||||
return null; // Sometimes the studio is unknown, so leave empty
|
||||
}
|
||||
|
||||
return trim($author->innertext);
|
||||
}
|
||||
|
||||
// Extracts the episode information (x of y released)
|
||||
private function extractAnimeEpisodeInformation($anime){
|
||||
$episode = $anime->find('div.header_info_episode', 0);
|
||||
if(!$episode)
|
||||
returnServerError('Could not find anime episode information!');
|
||||
return preg_replace('/\r|\n/', ' ', $episode->plaintext);
|
||||
$episode = $anime->find('div.header_info_episode', 0)
|
||||
or returnServerError('Could not find anime episode information!');
|
||||
|
||||
$retVal = preg_replace('/\r|\n/', ' ', $episode->plaintext);
|
||||
$retVal = preg_replace('/\s+/', ' ', $retVal);
|
||||
|
||||
return $retVal;
|
||||
}
|
||||
|
||||
// Extracts the background image
|
||||
@@ -73,15 +155,16 @@ class ShanaprojectBridge extends BridgeAbstract {
|
||||
// Getting the picture is a little bit tricky as it is part of the style.
|
||||
// Luckily the style is part of the parent div :)
|
||||
|
||||
if(preg_match('/url\(\/\/([^\)]+)\)/i', $anime->parent->style, $matches))
|
||||
if(preg_match('/url\(\/\/([^\)]+)\)/i', $anime->parent->style, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
returnServerError('Could not extract background image!');
|
||||
}
|
||||
|
||||
// Builds an URI to search for a specific anime (subber is left empty)
|
||||
private function buildAnimeSearchUri($anime){
|
||||
return $this->getURI()
|
||||
return self::URI
|
||||
. '/search/?title='
|
||||
. urlencode($this->extractAnimeTitle($anime))
|
||||
. '&subber=';
|
||||
@@ -102,22 +185,4 @@ class ShanaprojectBridge extends BridgeAbstract {
|
||||
. $this->buildAnimeSearchUri($anime)
|
||||
. '">Search episodes</a></p>';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = $this->loadSeasonAnimeList();
|
||||
|
||||
$animes = $html->find('div.header_display_box_info');
|
||||
if(!$animes)
|
||||
returnServerError('Could not find anime headers!');
|
||||
|
||||
foreach($animes as $anime) {
|
||||
$item = array();
|
||||
$item['title'] = $this->extractAnimeTitle($anime);
|
||||
$item['author'] = $this->extractAnimeAuthor($anime);
|
||||
$item['uri'] = $this->extractAnimeUri($anime);
|
||||
$item['timestamp'] = $this->extractAnimeTimestamp($anime);
|
||||
$item['content'] = $this->buildAnimeContent($anime);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,6 +16,8 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
|
||||
const CLIENT_ID = 'W0KEWWILAjDiRH89X0jpwzuq6rbSK08R';
|
||||
|
||||
private $feedIcon = null;
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$res = json_decode(getContents(
|
||||
@@ -25,6 +27,8 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
. self::CLIENT_ID
|
||||
)) or returnServerError('No results for this query');
|
||||
|
||||
$this->feedIcon = $res->avatar_url;
|
||||
|
||||
$tracks = json_decode(getContents(
|
||||
'https://api.soundcloud.com/users/'
|
||||
. urlencode($res->id)
|
||||
@@ -56,6 +60,14 @@ class SoundCloudBridge extends BridgeAbstract {
|
||||
|
||||
}
|
||||
|
||||
public function getIcon(){
|
||||
if ($this->feedIcon) {
|
||||
return $this->feedIcon;
|
||||
}
|
||||
|
||||
return parent::getIcon();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::NAME . ' - ' . $this->getInput('u');
|
||||
|
64
bridges/SplCenterBridge.php
Normal file
64
bridges/SplCenterBridge.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
class SplCenterBridge extends FeedExpander {
|
||||
|
||||
const NAME = 'Southern Poverty Law Center Bridge';
|
||||
const URI = 'https://www.splcenter.org';
|
||||
const DESCRIPTION = 'Returns the newest posts from the Southern Poverty Law Center';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'content' => array(
|
||||
'name' => 'Content',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'News' => 'news',
|
||||
'Hatewatch' => 'hatewatch',
|
||||
),
|
||||
'defaultValue' => 'news',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
protected function parseItem($item) {
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$articleHtml = getSimpleHTMLDOMCached($item['uri'])
|
||||
or returnServerError('Could not request: ' . $item['uri']);
|
||||
|
||||
foreach ($articleHtml->find('.file') as $index => $media) {
|
||||
$articleHtml->find('div.file', $index)->outertext = '<em>' . $media->outertext . '</em>';
|
||||
}
|
||||
|
||||
$item['content'] = $articleHtml->find('div#group-content-container', 0)->innertext;
|
||||
$item['enclosures'][] = $articleHtml->find('meta[name="twitter:image"]', 0)->content;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas($this->getURI() . '/rss.xml');
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('content'))) {
|
||||
return self::URI . '/' . $this->getInput('content');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('content'))) {
|
||||
$parameters = $this->getParameters();
|
||||
|
||||
$contentValues = array_flip($parameters[0]['content']['values']);
|
||||
|
||||
return $contentValues[$this->getInput('content')] . ' - Southern Poverty Law Center';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -8,44 +8,12 @@ class SteamBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'jacknumber';
|
||||
const PARAMETERS = array(
|
||||
'Wishlist' => array(
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'userid' => array(
|
||||
'name' => 'Steamid64 (find it on steamid.io)',
|
||||
'title' => 'User ID (17 digits). Find your user ID with steamid.io or steamidfinder.com',
|
||||
'required' => true,
|
||||
),
|
||||
'currency' => array(
|
||||
'name' => 'Currency',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
// source: http://steam.steamlytics.xyz/currencies
|
||||
'USD' => 'us',
|
||||
'GBP' => 'gb',
|
||||
'EUR' => 'fr',
|
||||
'CHF' => 'ch',
|
||||
'RUB' => 'ru',
|
||||
'BRL' => 'br',
|
||||
'JPY' => 'jp',
|
||||
'SEK' => 'se',
|
||||
'IDR' => 'id',
|
||||
'MYR' => 'my',
|
||||
'PHP' => 'ph',
|
||||
'SGD' => 'sg',
|
||||
'THB' => 'th',
|
||||
'KRW' => 'kr',
|
||||
'TRY' => 'tr',
|
||||
'MXN' => 'mx',
|
||||
'CAD' => 'ca',
|
||||
'NZD' => 'nz',
|
||||
'CNY' => 'cn',
|
||||
'INR' => 'in',
|
||||
'CLP' => 'cl',
|
||||
'PEN' => 'pe',
|
||||
'COP' => 'co',
|
||||
'ZAR' => 'za',
|
||||
'HKD' => 'hk',
|
||||
'TWD' => 'tw',
|
||||
'SRD' => 'sr',
|
||||
'AED' => 'ae',
|
||||
),
|
||||
'exampleValue' => '76561198821231205',
|
||||
'pattern' => '[0-9]{17}',
|
||||
),
|
||||
'only_discount' => array(
|
||||
'name' => 'Only discount',
|
||||
@@ -56,27 +24,15 @@ class SteamBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$username = $this->getInput('username');
|
||||
$params = array(
|
||||
'cc' => $this->getInput('currency')
|
||||
);
|
||||
$userid = $this->getInput('userid');
|
||||
|
||||
$url = self::URI . 'wishlist/id/' . $username . '?' . http_build_query($params);
|
||||
|
||||
$targetVariable = 'g_rgAppInfo';
|
||||
$sourceUrl = self::URI . 'wishlist/profiles/' . $userid . '/wishlistdata?p=0';
|
||||
$sort = array();
|
||||
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError("Could not request Steam Wishlist. Tried:\n - $url");
|
||||
$json = getContents($sourceUrl)
|
||||
or returnServerError('Could not get content from wishlistdata (' . $sourceUrl . ')');
|
||||
|
||||
$jsContent = $html->find('.responsive_page_template_content script', 0)->innertext;
|
||||
|
||||
if(preg_match('/var ' . $targetVariable . ' = (.*?);/s', $jsContent, $matches)) {
|
||||
$appsData = json_decode($matches[1]);
|
||||
} else {
|
||||
returnServerError("Could not parse JS variable ($targetVariable) in page content.");
|
||||
}
|
||||
$appsData = json_decode($json);
|
||||
|
||||
foreach($appsData as $id => $element) {
|
||||
|
||||
@@ -87,6 +43,8 @@ class SteamBridge extends BridgeAbstract {
|
||||
|
||||
if($element->subs) {
|
||||
$appIsBuyable = 1;
|
||||
$priceBlock = str_get_html($element->subs[0]->discount_block);
|
||||
$appPrice = str_replace('--', '00', $priceBlock->find('.discount_final_price', 0)->plaintext);
|
||||
|
||||
if($element->subs[0]->discount_pct) {
|
||||
|
||||
@@ -94,8 +52,6 @@ class SteamBridge extends BridgeAbstract {
|
||||
$discountBlock = str_get_html($element->subs[0]->discount_block);
|
||||
$appDiscountValue = $discountBlock->find('.discount_pct', 0)->plaintext;
|
||||
$appOldPrice = $discountBlock->find('.discount_original_price', 0)->plaintext;
|
||||
$appNewPrice = $discountBlock->find('.discount_final_price', 0)->plaintext;
|
||||
$appPrice = $appNewPrice;
|
||||
|
||||
} else {
|
||||
|
||||
@@ -103,7 +59,6 @@ class SteamBridge extends BridgeAbstract {
|
||||
continue;
|
||||
}
|
||||
|
||||
$appPrice = $element->subs[0]->price / 100;
|
||||
}
|
||||
|
||||
} else {
|
||||
@@ -117,11 +72,14 @@ class SteamBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
$coverUrl = str_replace('_292x136', '', strtok($element->capsule, '?'));
|
||||
$picturesPath = pathinfo($coverUrl)['dirname'] . '/';
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = "http://store.steampowered.com/app/$id/";
|
||||
$item['title'] = $element->name;
|
||||
$item['type'] = $appType;
|
||||
$item['cover'] = str_replace('_292x136', '', $element->capsule);
|
||||
$item['cover'] = $coverUrl;
|
||||
$item['timestamp'] = $element->added;
|
||||
$item['isBuyable'] = $appIsBuyable;
|
||||
$item['hasDiscount'] = $appHasDiscount;
|
||||
@@ -129,22 +87,29 @@ class SteamBridge extends BridgeAbstract {
|
||||
$item['priority'] = $element->priority;
|
||||
|
||||
if($appIsBuyable) {
|
||||
|
||||
$item['price'] = floatval(str_replace(',', '.', $appPrice));
|
||||
$item['content'] = $appPrice;
|
||||
|
||||
}
|
||||
|
||||
if($appIsFree) {
|
||||
$item['content'] = 'Free';
|
||||
}
|
||||
|
||||
if($appHasDiscount) {
|
||||
|
||||
$item['discount']['value'] = $appDiscountValue;
|
||||
$item['discount']['oldPrice'] = floatval(str_replace(',', '.', $appOldPrice));
|
||||
$item['discount']['newPrice'] = floatval(str_replace(',', '.', $appNewPrice));
|
||||
$item['discount']['oldPrice'] = $appOldPrice;
|
||||
$item['content'] = '<s>' . $appOldPrice . '</s> <b>' . $appPrice . '</b> (' . $appDiscountValue . ')';
|
||||
|
||||
}
|
||||
|
||||
$item['enclosures'] = array();
|
||||
$item['enclosures'][] = str_replace('_292x136', '', $element->capsule);
|
||||
$item['enclosures'][] = $coverUrl;
|
||||
|
||||
foreach($element->screenshots as $screenshot) {
|
||||
$item['enclosures'][] = substr($element->capsule, 0, -31) . $screenshot;
|
||||
foreach($element->screenshots as $screenshotFileName) {
|
||||
$item['enclosures'][] = $picturesPath . $screenshotFileName;
|
||||
}
|
||||
|
||||
$sort[$id] = $element->priority;
|
||||
|
191
bridges/SteamCommunityBridge.php
Normal file
191
bridges/SteamCommunityBridge.php
Normal file
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
class SteamCommunityBridge extends BridgeAbstract {
|
||||
const NAME = 'Steam Community';
|
||||
const URI = 'https://www.steamcommunity.com';
|
||||
const DESCRIPTION = 'Get the latest community updates for a game on Steam.';
|
||||
const MAINTAINER = 'thefranke';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'i' => array(
|
||||
'name' => 'App ID',
|
||||
'required' => true
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'category',
|
||||
'type' => 'list',
|
||||
'exampleValue' => 'Artwork',
|
||||
'title' => 'Select a category',
|
||||
'values' => array(
|
||||
'Artwork' => 'images',
|
||||
'Screenshots' => 'screenshots',
|
||||
'Videos' => 'videos',
|
||||
'Workshop' => 'workshop'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/favicon.ico';
|
||||
}
|
||||
|
||||
protected function getMainPage() {
|
||||
$category = $this->getInput('category');
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not fetch Steam data.');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
$category = $this->getInput('category');
|
||||
|
||||
if (is_null('i') || is_null($category)) {
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
$html = $this->getMainPage();
|
||||
|
||||
$titleItem = $html->find('div.apphub_AppName', 0);
|
||||
|
||||
if (!$titleItem)
|
||||
return self::NAME;
|
||||
|
||||
return $titleItem->innertext . ' (' . ucwords($category) . ')';
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if ($this->getInput('category') === 'workshop')
|
||||
return self::URI . '/workshop/browse/?appid='
|
||||
. $this->getInput('i') . '&browsesort=mostrecent';
|
||||
|
||||
return self::URI . '/app/'
|
||||
. $this->getInput('i') . '/'
|
||||
. $this->getInput('category')
|
||||
. '/?p=1&browsefilter=mostrecent';
|
||||
}
|
||||
|
||||
private function collectMedia() {
|
||||
$category = $this->getInput('category');
|
||||
$html = $this->getMainPage();
|
||||
$cards = $html->find('div.apphub_Card');
|
||||
|
||||
foreach($cards as $card) {
|
||||
$uri = $card->getAttribute('data-modal-content-url');
|
||||
|
||||
$htmlCard = getSimpleHTMLDOMCached($uri);
|
||||
|
||||
$author = $card->find('div.apphub_CardContentAuthorName', 0)->innertext;
|
||||
$author = strip_tags($author);
|
||||
|
||||
$title = $author . '\'s screenshot';
|
||||
|
||||
if ($category != 'screenshots')
|
||||
$title = $htmlCard->find('div.workshopItemTitle', 0)->innertext;
|
||||
|
||||
$date = $htmlCard->find('div.detailsStatRight', 0)->innertext;
|
||||
|
||||
// create item
|
||||
$item = array();
|
||||
$item['title'] = $title;
|
||||
$item['uri'] = $uri;
|
||||
$item['timestamp'] = strtotime($date);
|
||||
$item['author'] = $author;
|
||||
$item['categories'] = $category;
|
||||
|
||||
$media = $htmlCard->getElementById('ActualMedia');
|
||||
$mediaURI = $media->getAttribute('src');
|
||||
$downloadURI = $mediaURI;
|
||||
|
||||
if ($category == 'videos') {
|
||||
preg_match('/.*\/embed\/(.*)\?/', $mediaURI, $result);
|
||||
$youtubeID = $result[1];
|
||||
$mediaURI = 'https://img.youtube.com/vi/' . $youtubeID . '/hqdefault.jpg';
|
||||
$downloadURI = 'https://www.youtube.com/watch?v=' . $youtubeID;
|
||||
}
|
||||
|
||||
$desc = '';
|
||||
|
||||
if ($category == 'screenshots') {
|
||||
$descItem = $htmlCard->find('div.screenshotDescription', 0);
|
||||
if ($descItem)
|
||||
$desc = $descItem->innertext;
|
||||
}
|
||||
|
||||
if ($category == 'images') {
|
||||
$descItem = $htmlCard->find('div.nonScreenshotDescription', 0);
|
||||
if ($descItem)
|
||||
$desc = $descItem->innertext;
|
||||
$downloadURI = $htmlCard->find('a.downloadImage', 0)->href;
|
||||
}
|
||||
|
||||
$item['content'] = '<p><a href="' . $downloadURI . '"><img src="' . $mediaURI . '"/></a></p>';
|
||||
$item['content'] .= '<p>' . $desc . '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectWorkshop() {
|
||||
$category = $this->getInput('category');
|
||||
$html = $this->getMainPage();
|
||||
$workShopItems = $html->find('div.workshopItem');
|
||||
|
||||
foreach($workShopItems as $workShopItem) {
|
||||
$author = $workShopItem->find('div.workshopItemAuthorName', 0)->find('a', 0);
|
||||
$author = $author->innertext;
|
||||
|
||||
$fileRating = $workShopItem->find('img.fileRating', 0);
|
||||
|
||||
$uri = $workShopItem->find('a.ugc', 0)->getAttribute('href');
|
||||
|
||||
$htmlItem = getSimpleHTMLDOMCached($uri);
|
||||
|
||||
$title = $htmlItem->find('div.workshopItemTitle', 0)->innertext;
|
||||
$date = $htmlItem->find('div.detailsStatRight', 0)->innertext;
|
||||
$description = $htmlItem->find('div.workshopItemDescription', 0)->innertext;
|
||||
|
||||
$previewImage = $htmlItem->find('#previewImage', 0);
|
||||
|
||||
$htmlTags = $htmlItem->find('div.workshopTags');
|
||||
|
||||
$tags = '';
|
||||
|
||||
foreach($htmlTags as $htmlTag) {
|
||||
if ($tags !== '')
|
||||
$tags .= ',';
|
||||
|
||||
$tags .= $htmlTag->find('a', 0)->innertext;
|
||||
}
|
||||
|
||||
// create item
|
||||
$item = array();
|
||||
$item['title'] = $title;
|
||||
$item['uri'] = $uri;
|
||||
$item['timestamp'] = strtotime($date);
|
||||
$item['author'] = $author;
|
||||
$item['categories'] = $category;
|
||||
|
||||
$item['content'] = '<p><a href="' . $uri . '">'
|
||||
. $previewImage . '</a></p><p>' . $fileRating
|
||||
. '</p><p>' . $description . '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
if ($this->getInput('category') === 'workshop')
|
||||
$this->collectWorkshop();
|
||||
else
|
||||
$this->collectMedia();
|
||||
}
|
||||
}
|
80
bridges/StockFilingsBridge.php
Normal file
80
bridges/StockFilingsBridge.php
Normal file
@@ -0,0 +1,80 @@
|
||||
<?php
|
||||
|
||||
class StockFilingsBridge extends FeedExpander {
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const NAME = 'SEC Stock filings';
|
||||
const URI = 'https://www.sec.gov/edgar/searchedgar/companysearch.html';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Tracks SEC Filings for a single company';
|
||||
const SEARCH_URL = 'https://www.sec.gov/cgi-bin/browse-edgar?owner=exclude&action=getcompany&CIK=';
|
||||
const WEBSITE_ROOT = 'https://www.sec.gov';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'ticker' => array(
|
||||
'name' => 'cik',
|
||||
'required' => true,
|
||||
'exampleValue' => 'AMD',
|
||||
// https://stackoverflow.com/a/12827734
|
||||
'pattern' => '[A-Za-z0-9]+',
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.sec.gov/favicon.ico';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates search URL
|
||||
*/
|
||||
private function getSearchUrl() {
|
||||
return self::SEARCH_URL . $this->getInput('ticker');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Company Name
|
||||
*/
|
||||
private function getRssFeed($html) {
|
||||
$links = $html->find('#contentDiv a');
|
||||
|
||||
foreach ($links as $link) {
|
||||
$href = $link->href;
|
||||
|
||||
if (substr($href, 0, 4) !== 'http') {
|
||||
$href = self::WEBSITE_ROOT . $href;
|
||||
}
|
||||
parse_str(html_entity_decode(parse_url($href, PHP_URL_QUERY)), $query);
|
||||
|
||||
if (isset($query['output']) and ($query['output'] == 'atom')) {
|
||||
return $href;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return \simple_html_dom object
|
||||
* for the entire html of the product page
|
||||
*/
|
||||
private function getHtml() {
|
||||
$uri = $this->getSearchUrl();
|
||||
|
||||
return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request SEC.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape the SEC Stock Filings RSS Feed URL
|
||||
* and redirect there
|
||||
*/
|
||||
public function collectData() {
|
||||
$html = $this->getHtml();
|
||||
$rssFeedUrl = $this->getRssFeed($html);
|
||||
|
||||
if ($rssFeedUrl) {
|
||||
parent::collectExpandableDatas($rssFeedUrl);
|
||||
} else {
|
||||
returnClientError('Could not find RSS Feed URL. Are you sure you used a valid CIK?');
|
||||
}
|
||||
}
|
||||
}
|
47
bridges/StoriesIGBridge.php
Normal file
47
bridges/StoriesIGBridge.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
class StoriesIGBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Instagram Stories';
|
||||
const URI = 'https://storiesig.com';
|
||||
const DESCRIPTION = 'Display Instagram Stories';
|
||||
const MAINTAINER = 'antoineturmel';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'username' => array(
|
||||
'name' => 'Instagram username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert the username here'
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
|
||||
$results = $html->find('article');
|
||||
|
||||
foreach($results as $result) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $this->getInput('username') . ' story';
|
||||
$item['uri'] = $result->find('div.download', 0)->find('a', 0)->href;
|
||||
$item['author'] = $this->getInput('username');
|
||||
$item['uid'] = $result->find('time', 0)->datetime;
|
||||
|
||||
$item['content'] = $result;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
$uri = self::URI . '/stories/';
|
||||
$uri .= urlencode($this->getInput('username'));
|
||||
return $uri;
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
301
bridges/TelegramBridge.php
Normal file
301
bridges/TelegramBridge.php
Normal file
@@ -0,0 +1,301 @@
|
||||
<?php
|
||||
class TelegramBridge extends BridgeAbstract {
|
||||
const NAME = 'Telegram Bridge';
|
||||
const URI = 'https://t.me';
|
||||
const DESCRIPTION = 'Returns newest posts from a public Telegram channel';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '@telegram',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 900; // 15 mins
|
||||
|
||||
private $feedName = '';
|
||||
private $enclosures = array();
|
||||
private $itemTitle = '';
|
||||
|
||||
private $backgroundImageRegex = "/background-image:url\('(.*)'\)/";
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
$channelTitle = htmlspecialchars_decode(
|
||||
$html->find('div.tgme_channel_info_header_title span', 0)->plaintext,
|
||||
ENT_QUOTES
|
||||
);
|
||||
$this->feedName = $channelTitle . ' (@' . $this->processUsername() . ')';
|
||||
|
||||
foreach($html->find('div.tgme_widget_message_wrap.js-widget_message_wrap') as $index => $messageDiv) {
|
||||
$this->itemTitle = '';
|
||||
$this->enclosures = array();
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->processUri($messageDiv);
|
||||
$item['content'] = html_entity_decode($this->processContent($messageDiv), ENT_QUOTES);
|
||||
$item['title'] = html_entity_decode($this->itemTitle, ENT_QUOTES);
|
||||
$item['timestamp'] = $this->processDate($messageDiv);
|
||||
$item['enclosures'] = $this->enclosures;
|
||||
$author = trim($messageDiv->find('a.tgme_widget_message_owner_name', 0)->plaintext);
|
||||
$item['author'] = html_entity_decode($author, ENT_QUOTES);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
$this->items = array_reverse($this->items);
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('username'))) {
|
||||
return self::URI . '/s/' . $this->processUsername();
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
if (!empty($this->feedName)) {
|
||||
return $this->feedName . ' - Telegram';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function processUsername() {
|
||||
|
||||
if (substr($this->getInput('username'), 0, 1) === '@') {
|
||||
return substr($this->getInput('username'), 1);
|
||||
}
|
||||
|
||||
return $this->getInput('username');
|
||||
}
|
||||
|
||||
private function processUri($messageDiv) {
|
||||
return $messageDiv->find('a.tgme_widget_message_date', 0)->href;
|
||||
}
|
||||
|
||||
private function processContent($messageDiv) {
|
||||
$message = '';
|
||||
|
||||
if ($messageDiv->find('div.tgme_widget_message_forwarded_from', 0)) {
|
||||
$message = $messageDiv->find('div.tgme_widget_message_forwarded_from', 0)->innertext . '<br><br>';
|
||||
}
|
||||
|
||||
if ($messageDiv->find('a.tgme_widget_message_reply', 0)) {
|
||||
$message = $this->processReply($messageDiv);
|
||||
}
|
||||
|
||||
if ($messageDiv->find('div.tgme_widget_message_sticker_wrap', 0)) {
|
||||
$message .= $this->processSticker($messageDiv);
|
||||
}
|
||||
|
||||
if ($messageDiv->find('div.tgme_widget_message_poll', 0)) {
|
||||
$message .= $this->processPoll($messageDiv);
|
||||
}
|
||||
|
||||
if ($messageDiv->find('video', 0)) {
|
||||
$message .= $this->processVideo($messageDiv);
|
||||
}
|
||||
|
||||
if ($messageDiv->find('a.tgme_widget_message_photo_wrap', 0)) {
|
||||
$message .= $this->processPhoto($messageDiv);
|
||||
}
|
||||
|
||||
if ($messageDiv->find('a.not_supported', 0)) {
|
||||
$message .= $this->processNotSupported($messageDiv);
|
||||
}
|
||||
|
||||
if ($messageDiv->find('div.tgme_widget_message_text.js-message_text', 0)) {
|
||||
$message .= $messageDiv->find('div.tgme_widget_message_text.js-message_text', 0);
|
||||
|
||||
$this->itemTitle = $this->ellipsisTitle(
|
||||
$messageDiv->find('div.tgme_widget_message_text.js-message_text', 0)->plaintext
|
||||
);
|
||||
}
|
||||
|
||||
if ($messageDiv->find('a.tgme_widget_message_link_preview', 0)) {
|
||||
$message .= $this->processLinkPreview($messageDiv);
|
||||
}
|
||||
|
||||
return $message;
|
||||
}
|
||||
|
||||
private function processReply($messageDiv) {
|
||||
|
||||
$reply = $messageDiv->find('a.tgme_widget_message_reply', 0);
|
||||
|
||||
return <<<EOD
|
||||
<blockquote>{$reply->find('span.tgme_widget_message_author_name', 0)->plaintext}<br>
|
||||
{$reply->find('div.tgme_widget_message_text', 0)->innertext}
|
||||
<a href="{$reply->href}">{$reply->href}</a></blockquote><hr>
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function processSticker($messageDiv) {
|
||||
|
||||
if (empty($this->itemTitle)) {
|
||||
$this->itemTitle = '@' . $this->processUsername() . ' posted a sticker';
|
||||
}
|
||||
|
||||
$stickerDiv = $messageDiv->find('div.tgme_widget_message_sticker_wrap', 0);
|
||||
|
||||
preg_match($this->backgroundImageRegex, $stickerDiv->find('i', 0)->style, $sticker);
|
||||
|
||||
$this->enclosures[] = $sticker[1];
|
||||
|
||||
return <<<EOD
|
||||
<a href="{$stickerDiv->children(0)->herf}"><img src="{$sticker[1]}"></a>
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function processPoll($messageDiv) {
|
||||
|
||||
$poll = $messageDiv->find('div.tgme_widget_message_poll', 0);
|
||||
|
||||
$title = $poll->find('div.tgme_widget_message_poll_question', 0)->plaintext;
|
||||
$type = $poll->find('div.tgme_widget_message_poll_type', 0)->plaintext;
|
||||
|
||||
if (empty($this->itemTitle)) {
|
||||
$this->itemTitle = $title;
|
||||
}
|
||||
|
||||
$pollOptions = '<ul>';
|
||||
|
||||
foreach ($poll->find('div.tgme_widget_message_poll_option') as $option) {
|
||||
$pollOptions .= '<li>' . $option->children(0)->plaintext . ' - ' .
|
||||
$option->find('div.tgme_widget_message_poll_option_text', 0)->plaintext . '</li>';
|
||||
}
|
||||
$pollOptions .= '</ul>';
|
||||
|
||||
return <<<EOD
|
||||
{$title}<br><small>$type</small><br>{$pollOptions}
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function processLinkPreview($messageDiv) {
|
||||
|
||||
$image = '';
|
||||
$title = '';
|
||||
$site = '';
|
||||
$description = '';
|
||||
|
||||
$preview = $messageDiv->find('a.tgme_widget_message_link_preview', 0);
|
||||
|
||||
if (trim($preview->innertext) === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if($preview->find('i', 0) &&
|
||||
preg_match($this->backgroundImageRegex, $preview->find('i', 0)->style, $photo)) {
|
||||
|
||||
$image = '<img src="' . $photo[1] . '"/>';
|
||||
$this->enclosures[] = $photo[1];
|
||||
}
|
||||
|
||||
if ($preview->find('div.link_preview_title', 0)) {
|
||||
$title = $preview->find('div.link_preview_title', 0)->plaintext;
|
||||
}
|
||||
|
||||
if ($preview->find('div.link_preview_site_name', 0)) {
|
||||
$site = $preview->find('div.link_preview_site_name', 0)->plaintext;
|
||||
}
|
||||
|
||||
if ($preview->find('div.link_preview_description', 0)) {
|
||||
$description = $preview->find('div.link_preview_description', 0)->plaintext;
|
||||
}
|
||||
|
||||
return <<<EOD
|
||||
<blockquote><a href="{$preview->href}">$image</a><br><a href="{$preview->href}">
|
||||
{$title} - {$site}</a><br>{$description}</blockquote>
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function processVideo($messageDiv) {
|
||||
|
||||
if (empty($this->itemTitle)) {
|
||||
$this->itemTitle = '@' . $this->processUsername() . ' posted a video';
|
||||
}
|
||||
|
||||
if ($messageDiv->find('i.tgme_widget_message_video_thumb')) {
|
||||
preg_match($this->backgroundImageRegex, $messageDiv->find('i.tgme_widget_message_video_thumb', 0)->style, $photo);
|
||||
} elseif ($messageDiv->find('i.link_preview_video_thumb')) {
|
||||
preg_match($this->backgroundImageRegex, $messageDiv->find('i.link_preview_video_thumb', 0)->style, $photo);
|
||||
}
|
||||
|
||||
$this->enclosures[] = $photo[1];
|
||||
|
||||
return <<<EOD
|
||||
<video controls="" poster="{$photo[1]}" preload="none">
|
||||
<source src="{$messageDiv->find('video', 0)->src}" type="video/mp4">
|
||||
</video>
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function processPhoto($messageDiv) {
|
||||
|
||||
if (empty($this->itemTitle)) {
|
||||
$this->itemTitle = '@' . $this->processUsername() . ' posted a photo';
|
||||
}
|
||||
|
||||
$photos = '';
|
||||
|
||||
foreach ($messageDiv->find('a.tgme_widget_message_photo_wrap') as $photoWrap) {
|
||||
preg_match($this->backgroundImageRegex, $photoWrap->style, $photo);
|
||||
|
||||
$this->enclosures[] = $photo[1];
|
||||
|
||||
$photos .= <<<EOD
|
||||
<a href="{$photoWrap->href}"><img src="{$photo[1]}"/></a><br>
|
||||
EOD;
|
||||
}
|
||||
return $photos;
|
||||
}
|
||||
|
||||
private function processNotSupported($messageDiv) {
|
||||
|
||||
if (empty($this->itemTitle)) {
|
||||
$this->itemTitle = '@' . $this->processUsername() . ' posted a video';
|
||||
}
|
||||
|
||||
if ($messageDiv->find('i.tgme_widget_message_video_thumb')) {
|
||||
preg_match($this->backgroundImageRegex, $messageDiv->find('i.tgme_widget_message_video_thumb', 0)->style, $photo);
|
||||
} elseif ($messageDiv->find('i.link_preview_video_thumb')) {
|
||||
preg_match($this->backgroundImageRegex, $messageDiv->find('i.link_preview_video_thumb', 0)->style, $photo);
|
||||
}
|
||||
|
||||
$this->enclosures[] = $photo[1];
|
||||
|
||||
return <<<EOD
|
||||
<a href="{$messageDiv->find('a.not_supported', 0)->href}">
|
||||
{$messageDiv->find('div.message_media_not_supported_label', 0)->innertext}<br><br>
|
||||
{$messageDiv->find('span.message_media_view_in_telegram', 0)->innertext}<br><br>
|
||||
<img src="{$photo[1]}"/></a>
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function processDate($messageDiv) {
|
||||
|
||||
$messageMeta = $messageDiv->find('span.tgme_widget_message_meta', 0);
|
||||
return $messageMeta->find('time', 0)->datetime;
|
||||
|
||||
}
|
||||
|
||||
private function ellipsisTitle($text) {
|
||||
|
||||
$length = 100;
|
||||
|
||||
if (strlen($text) > $length) {
|
||||
$text = explode('<br>', wordwrap($text, $length, '<br>'));
|
||||
return $text[0] . '...';
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
96
bridges/TheGuardianBridge.php
Normal file
96
bridges/TheGuardianBridge.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
class TheGuardianBridge extends FeedExpander {
|
||||
const MAINTAINER = 'IceWreck';
|
||||
const NAME = 'The Guardian Bridge';
|
||||
const URI = 'https://www.theguardian.com/';
|
||||
const CACHE_TIMEOUT = 600; // This is a news site, so don't cache for more than 10 mins
|
||||
const DESCRIPTION = 'RSS feed for The Guardian';
|
||||
const PARAMETERS = array( array(
|
||||
'feed' => array(
|
||||
'name' => 'Feed',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'World News' => 'world/rss',
|
||||
'US News' => '/us-news/rss',
|
||||
'UK News' => '/uk-news/rss',
|
||||
'Europe News' => '/world/europe-news/rss',
|
||||
'Asia News' => '/world/asia/rss',
|
||||
'Tech' => '/uk/technology/rss',
|
||||
'Business News' => '/uk/business/rss',
|
||||
'Opinion' => '/uk/commentisfree/rss',
|
||||
'Lifestyle' => '/uk/lifeandstyle/rss',
|
||||
'Culture' => '/uk/culture/rss',
|
||||
'Sports' => '/uk/sport/rss'
|
||||
)
|
||||
)
|
||||
|
||||
/*
|
||||
|
||||
Topicwise Links
|
||||
|
||||
You can find the base feed for any topic by appending /rss to the url.
|
||||
|
||||
Example:
|
||||
|
||||
https://feeds.theguardian.com/theguardian/uk-news/rss
|
||||
https://feeds.theguardian.com/theguardian/us-news/rss
|
||||
|
||||
Or simply
|
||||
|
||||
https://www.theguardian.com/world/rss
|
||||
|
||||
Just add that topic as a value in the PARAMETERS const.
|
||||
|
||||
*/
|
||||
|
||||
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$feed = $this->getInput('feed');
|
||||
$feedURL = 'https://feeds.theguardian.com/theguardian/' . $feed;
|
||||
$this->collectExpandableDatas($feedURL, 10);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
|
||||
// --- Recovering the article ---
|
||||
|
||||
// $articlePage gets the entire page's contents
|
||||
$articlePage = getSimpleHTMLDOM($newsItem->link);
|
||||
// figure contain's the main article image
|
||||
$article = $articlePage->find('figure', 0);
|
||||
// content__article-body has the actual article
|
||||
foreach($articlePage->find('.content__article-body') as $element)
|
||||
$article = $article . $element;
|
||||
|
||||
// --- Fixing ugly elements ---
|
||||
|
||||
// Replace the image viewer and BS with the image itself
|
||||
foreach($articlePage->find('a.article__img-container') as $uslElementLoc) {
|
||||
$main_img = $uslElementLoc->find('img', 0);
|
||||
$article = str_replace($uslElementLoc, $main_img, $article);
|
||||
}
|
||||
|
||||
// List of all the crap in the article
|
||||
$uselessElements = array(
|
||||
'#show-caption',
|
||||
'.element-atom',
|
||||
'.submeta',
|
||||
'youtube-media-atom',
|
||||
'svg'
|
||||
);
|
||||
|
||||
// Remove the listed crap
|
||||
foreach($uselessElements as $uslElement) {
|
||||
foreach($articlePage->find($uslElement) as $uslElementLoc) {
|
||||
$article = str_replace($uslElementLoc, '', $article);
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = $article;
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ class ThePirateBayBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'The Pirate Bay';
|
||||
const URI = 'https://thepiratebay.wf/';
|
||||
const URI = 'https://thepiratebay.org/';
|
||||
const DESCRIPTION = 'Returns results for the keywords. You can put several
|
||||
list of keywords by separating them with a semicolon (e.g. "one show;another
|
||||
show"). Category based search needs the category number as input. User based
|
||||
@@ -149,11 +149,12 @@ class ThePirateBayBridge extends BridgeAbstract {
|
||||
|| !is_null($element->find('img[alt=VIP]', 0))
|
||||
|| !is_null($element->find('img[alt=Trusted]', 0))) {
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 3)->href;
|
||||
$item['uri'] = self::URI . $element->find('a.detLink', 0)->href;
|
||||
$item['id'] = self::URI . $element->find('a.detLink', 0)->href;
|
||||
$item['timestamp'] = parseDateTimestamp($element);
|
||||
$item['author'] = $element->find('a.detDesc', 0)->plaintext;
|
||||
$item['title'] = $element->find('a.detLink', 0)->plaintext;
|
||||
$item['magnet'] = $element->find('a', 3)->href;
|
||||
$item['seeders'] = (int)$element->find('td', 2)->plaintext;
|
||||
$item['leechers'] = (int)$element->find('td', 3)->plaintext;
|
||||
$item['content'] = $element->find('font', 0)->plaintext
|
||||
@@ -163,7 +164,9 @@ class ThePirateBayBridge extends BridgeAbstract {
|
||||
. $item['leechers']
|
||||
. '<br><a href="'
|
||||
. $item['id']
|
||||
. '">info page</a>';
|
||||
. '">info page</a><br><a href="'
|
||||
. $item['magnet']
|
||||
. '">magnet link</a>';
|
||||
|
||||
if(isset($item['title']))
|
||||
$this->items[] = $item;
|
||||
|
202
bridges/TwitchBridge.php
Normal file
202
bridges/TwitchBridge.php
Normal file
@@ -0,0 +1,202 @@
|
||||
<?php
|
||||
class TwitchBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Roliga';
|
||||
const NAME = 'Twitch Bridge';
|
||||
const URI = 'https://twitch.tv/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Twitch channel videos';
|
||||
const PARAMETERS = array( array(
|
||||
'channel' => array(
|
||||
'name' => 'Channel',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Lowercase channel name as seen in channel URL'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Archive' => 'archive',
|
||||
'Highlights' => 'highlight',
|
||||
'Uploads' => 'upload'
|
||||
),
|
||||
'defaultValue' => 'archive'
|
||||
)
|
||||
));
|
||||
|
||||
/*
|
||||
* Official instructions for obtaining your own client ID can be found here:
|
||||
* https://dev.twitch.tv/docs/v5/#getting-a-client-id
|
||||
*/
|
||||
const CLIENT_ID = 'kimne78kx3ncx6brgo4mv6wki5h1ko';
|
||||
|
||||
public function collectData(){
|
||||
// get channel user
|
||||
$query_data = array(
|
||||
'login' => $this->getInput('channel')
|
||||
);
|
||||
$users = $this->apiGet('users', $query_data)->users;
|
||||
if(count($users) === 0)
|
||||
returnClientError('User "'
|
||||
. $this->getInput('channel')
|
||||
. '" could not be found');
|
||||
$user = $users[0];
|
||||
|
||||
// get video list
|
||||
$query_endpoint = 'channels/' . $user->_id . '/videos';
|
||||
$query_data = array(
|
||||
'broadcast_type' => $this->getInput('type'),
|
||||
'limit' => 10
|
||||
);
|
||||
$videos = $this->apiGet($query_endpoint, $query_data)->videos;
|
||||
|
||||
foreach($videos as $video) {
|
||||
$item = array(
|
||||
'uri' => $video->url,
|
||||
'title' => $video->title,
|
||||
'timestamp' => $video->published_at,
|
||||
'author' => $video->channel->display_name,
|
||||
);
|
||||
|
||||
// Add categories for tags and played game
|
||||
$item['categories'] = array_filter(explode(' ', $video->tag_list));
|
||||
if(!empty($video->game))
|
||||
$item['categories'][] = $video->game;
|
||||
|
||||
// Add enclosures for thumbnails from a few points in the video
|
||||
$item['enclosures'] = array();
|
||||
foreach($video->thumbnails->large as $thumbnail)
|
||||
$item['enclosures'][] = $thumbnail->url;
|
||||
|
||||
/*
|
||||
* Content format example:
|
||||
*
|
||||
* [Preview Image]
|
||||
*
|
||||
* Some optional video description.
|
||||
*
|
||||
* Duration: 1:23:45
|
||||
* Views: 123
|
||||
*
|
||||
* Played games:
|
||||
* * 00:00:00 Game 1
|
||||
* * 00:12:34 Game 2
|
||||
*
|
||||
*/
|
||||
$item['content'] = '<p><a href="'
|
||||
. $video->url
|
||||
. '"><img src="'
|
||||
. $video->preview->large
|
||||
. '" /></a></p><p>'
|
||||
. $video->description_html
|
||||
. '</p><p><b>Duration:</b> '
|
||||
. $this->formatTimestampTime($video->length)
|
||||
. '<br/><b>Views:</b> '
|
||||
. $video->views
|
||||
. '</p>';
|
||||
|
||||
// Add played games list to content
|
||||
$video_id = trim($video->_id, 'v'); // _id gives 'v1234' but API wants '1234'
|
||||
$markers = $this->apiGet('videos/' . $video_id . '/markers')->markers;
|
||||
$item['content'] .= '<p><b>Played games:</b></b><ul><li><a href="'
|
||||
. $video->url
|
||||
. '">00:00:00</a> - '
|
||||
. $video->game
|
||||
. '</li>';
|
||||
if(isset($markers->game_changes)) {
|
||||
usort($markers->game_changes, function($a, $b) {
|
||||
return $a->time - $b->time;
|
||||
});
|
||||
foreach($markers->game_changes as $game_change) {
|
||||
$item['categories'][] = $game_change->label;
|
||||
$item['content'] .= '<li><a href="'
|
||||
. $video->url
|
||||
. '?t='
|
||||
. $this->formatQueryTime($game_change->time)
|
||||
. '">'
|
||||
. $this->formatTimestampTime($game_change->time)
|
||||
. '</a> - '
|
||||
. $game_change->label
|
||||
. '</li>';
|
||||
}
|
||||
}
|
||||
$item['content'] .= '</ul></p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
// e.g. 01:53:27
|
||||
private function formatTimestampTime($seconds) {
|
||||
return sprintf('%02d:%02d:%02d',
|
||||
floor($seconds / 3600),
|
||||
($seconds / 60) % 60,
|
||||
$seconds % 60);
|
||||
}
|
||||
|
||||
// e.g. 01h53m27s
|
||||
private function formatQueryTime($seconds) {
|
||||
return sprintf('%02dh%02dm%02ds',
|
||||
floor($seconds / 3600),
|
||||
($seconds / 60) % 60,
|
||||
$seconds % 60);
|
||||
}
|
||||
|
||||
/*
|
||||
* Ideally the new 'helix' API should be used as v5/'kraken' is deprecated.
|
||||
* The new API however still misses many features (markers, played game..) of
|
||||
* the old one, so let's use the old one for as long as it's available.
|
||||
*/
|
||||
private function apiGet($endpoint, $query_data = array()) {
|
||||
$query_data['api_version'] = 5;
|
||||
$url = 'https://api.twitch.tv/kraken/'
|
||||
. $endpoint
|
||||
. '?'
|
||||
. http_build_query($query_data);
|
||||
$header = array(
|
||||
'Client-ID: ' . self::CLIENT_ID
|
||||
);
|
||||
|
||||
$data = json_decode(getContents($url, $header))
|
||||
or returnServerError('API request to "' . $url . '" failed.');
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('channel'))) {
|
||||
return $this->getInput('channel') . ' twitch videos';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('channel'))) {
|
||||
return self::URI . $this->getInput('channel');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function detectParameters($url){
|
||||
$params = array();
|
||||
|
||||
// Matches e.g. https://www.twitch.tv/someuser/videos?filter=archives
|
||||
$regex = '/^(https?:\/\/)?
|
||||
(www\.)?
|
||||
twitch\.tv\/
|
||||
([^\/&?\n]+)
|
||||
\/videos\?.*filter=
|
||||
(all|archive|highlight|upload)/x';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['channel'] = urldecode($matches[3]);
|
||||
$params['type'] = $matches[4];
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@@ -28,7 +28,31 @@ class TwitterBridge extends BridgeAbstract {
|
||||
'name' => 'Keyword or #hashtag',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rss-bridge, #rss-bridge',
|
||||
'title' => 'Insert a keyword or hashtag'
|
||||
'title' => <<<EOD
|
||||
* To search for multiple words (must contain all of these words), put a space between them.
|
||||
|
||||
Example: `rss-bridge release`.
|
||||
|
||||
* To search for multiple words (contains any of these words), put "OR" between them.
|
||||
|
||||
Example: `rss-bridge OR rssbridge`.
|
||||
|
||||
* To search for an exact phrase (including whitespace), put double-quotes around them.
|
||||
|
||||
Example: `"rss-bridge release"`
|
||||
|
||||
* If you want to search for anything **but** a specific word, put a hyphen before it.
|
||||
|
||||
Example: `rss-bridge -release` (ignores "release")
|
||||
|
||||
* Of course, this also works for hashtags.
|
||||
|
||||
Example: `#rss-bridge OR #rssbridge`
|
||||
|
||||
* And you can combine them in any shape or form you like.
|
||||
|
||||
Example: `#rss-bridge OR #rssbridge -release`
|
||||
EOD
|
||||
)
|
||||
),
|
||||
'By username' => array(
|
||||
@@ -146,8 +170,15 @@ class TwitterBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$page = $this->getURI();
|
||||
|
||||
if(php_sapi_name() === 'cli' && empty(ini_get('curl.cainfo'))) {
|
||||
$cookies = $this->getCookies($page);
|
||||
$html = getSimpleHTMLDOM($page, array("Cookie: $cookies"));
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM($page, array(), array(CURLOPT_COOKIEFILE => ''));
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
if(!$html) {
|
||||
switch($this->queriedContext) {
|
||||
case 'By keyword or hashtag':
|
||||
@@ -165,7 +196,7 @@ class TwitterBridge extends BridgeAbstract {
|
||||
|
||||
// Skip retweets?
|
||||
if($this->getInput('noretweet')
|
||||
&& strcasecmp($tweet->getAttribute('data-screen-name'), $this->getInput('u'))) {
|
||||
&& $tweet->find('div.context span.js-retweet-text a', 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -189,8 +220,8 @@ class TwitterBridge extends BridgeAbstract {
|
||||
$item['fullname'] = htmlspecialchars_decode($tweet->getAttribute('data-name'), ENT_QUOTES);
|
||||
// get author
|
||||
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
|
||||
if(strcasecmp($tweet->getAttribute('data-screen-name'), $this->getInput('u'))) {
|
||||
$item['author'] .= ' RT: @' . $this->getInput('u');
|
||||
if($rt = $tweet->find('div.context span.js-retweet-text a', 0)) {
|
||||
$item['author'] .= ' RT: @' . $rt->plaintext;
|
||||
}
|
||||
// get avatar link
|
||||
$item['avatar'] = $tweet->find('img', 0)->src;
|
||||
@@ -245,22 +276,26 @@ EOD;
|
||||
|
||||
// Add embeded image to content
|
||||
$image_html = '';
|
||||
$image = $this->getImageURI($tweet);
|
||||
if(!$this->getInput('noimg') && !is_null($image)) {
|
||||
// Set image scaling
|
||||
$image_orig = $this->getInput('noimgscaling') ? $image : $image . ':orig';
|
||||
$image_thumb = $this->getInput('noimgscaling') ? $image : $image . ':thumb';
|
||||
$images = $this->getImageURI($tweet);
|
||||
if(!$this->getInput('noimg') && !is_null($images)) {
|
||||
|
||||
// add enclosures
|
||||
$item['enclosures'] = array($image_orig);
|
||||
foreach ($images as $image) {
|
||||
|
||||
$image_html = <<<EOD
|
||||
// Set image scaling
|
||||
$image_orig = $this->getInput('noimgscaling') ? $image : $image . ':orig';
|
||||
$image_thumb = $this->getInput('noimgscaling') ? $image : $image . ':thumb';
|
||||
|
||||
// add enclosures
|
||||
$item['enclosures'][] = $image_orig;
|
||||
|
||||
$image_html .= <<<EOD
|
||||
<a href="{$image_orig}">
|
||||
<img
|
||||
style="align:top; max-width:558px; border:1px solid black;"
|
||||
src="{$image_thumb}" />
|
||||
</a>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
|
||||
// add content
|
||||
@@ -291,22 +326,27 @@ EOD;
|
||||
|
||||
// Add embeded image to content
|
||||
$quotedImage_html = '';
|
||||
$quotedImage = $this->getQuotedImageURI($tweet);
|
||||
if(!$this->getInput('noimg') && !is_null($quotedImage)) {
|
||||
// Set image scaling
|
||||
$quotedImage_orig = $this->getInput('noimgscaling') ? $quotedImage : $quotedImage . ':orig';
|
||||
$quotedImage_thumb = $this->getInput('noimgscaling') ? $quotedImage : $quotedImage . ':thumb';
|
||||
$quotedImages = $this->getQuotedImageURI($tweet);
|
||||
|
||||
// add enclosures
|
||||
$item['enclosures'] = array($quotedImage_orig);
|
||||
if(!$this->getInput('noimg') && !is_null($quotedImages)) {
|
||||
|
||||
$quotedImage_html = <<<EOD
|
||||
<a href="{$quotedImage_orig}">
|
||||
foreach ($quotedImages as $image) {
|
||||
|
||||
// Set image scaling
|
||||
$image_orig = $this->getInput('noimgscaling') ? $image : $image . ':orig';
|
||||
$image_thumb = $this->getInput('noimgscaling') ? $image : $image . ':thumb';
|
||||
|
||||
// add enclosures
|
||||
$item['enclosures'][] = $image_orig;
|
||||
|
||||
$quotedImage_html .= <<<EOD
|
||||
<a href="{$image_orig}">
|
||||
<img
|
||||
style="align:top; max-width:558px; border:1px solid black;"
|
||||
src="{$quotedImage_thumb}" />
|
||||
src="{$image_thumb}" />
|
||||
</a>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = <<<EOD
|
||||
@@ -360,9 +400,18 @@ EOD;
|
||||
|
||||
private function getImageURI($tweet){
|
||||
// Find media in tweet
|
||||
$images = array();
|
||||
|
||||
$container = $tweet->find('div.AdaptiveMedia-container', 0);
|
||||
|
||||
if($container && $container->find('img', 0)) {
|
||||
return $container->find('img', 0)->src;
|
||||
foreach ($container->find('img') as $img) {
|
||||
$images[] = $img->src;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($images)) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
return null;
|
||||
@@ -370,11 +419,43 @@ EOD;
|
||||
|
||||
private function getQuotedImageURI($tweet){
|
||||
// Find media in tweet
|
||||
$images = array();
|
||||
|
||||
$container = $tweet->find('div.QuoteMedia-container', 0);
|
||||
|
||||
if($container && $container->find('img', 0)) {
|
||||
return $container->find('img', 0)->src;
|
||||
foreach ($container->find('img') as $img) {
|
||||
$images[] = $img->src;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($images)) {
|
||||
return $images;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getCookies($pageURL){
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'http' => array(
|
||||
'follow_location' => false
|
||||
)
|
||||
)
|
||||
);
|
||||
$a = file_get_contents($pageURL, 0, $ctx);
|
||||
|
||||
//First request to get the cookie
|
||||
$cookies = '';
|
||||
foreach($http_response_header as $hdr) {
|
||||
if(stripos($hdr, 'Set-Cookie') !== false) {
|
||||
$cLine = explode(':', $hdr)[1];
|
||||
$cLine = explode(';', $cLine)[0];
|
||||
$cookies .= ';' . $cLine;
|
||||
}
|
||||
}
|
||||
|
||||
return substr($cookies, 2);
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class UnsplashBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'nel50n';
|
||||
const NAME = 'Unsplash Bridge';
|
||||
const URI = 'http://unsplash.com/';
|
||||
const URI = 'https://unsplash.com/';
|
||||
const CACHE_TIMEOUT = 43200; // 12h
|
||||
const DESCRIPTION = 'Returns the latests photos from Unsplash';
|
||||
|
||||
@@ -27,51 +27,42 @@ class UnsplashBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$width = $this->getInput('w');
|
||||
$num = 0;
|
||||
$max = $this->getInput('m');
|
||||
$quality = $this->getInput('q');
|
||||
$lastpage = 1;
|
||||
|
||||
for($page = 1; $page <= $lastpage; $page++) {
|
||||
$link = self::URI . '/grid?page=' . $page;
|
||||
$html = getSimpleHTMLDOM($link)
|
||||
or returnServerError('No results for this query.');
|
||||
$api_response = getContents('https://unsplash.com/napi/photos?page=1&per_page=' . $max)
|
||||
or returnServerError('Could not request Unsplash API.');
|
||||
$json = json_decode($api_response, true);
|
||||
|
||||
if($page === 1) {
|
||||
preg_match(
|
||||
'/=(\d+)$/',
|
||||
$html->find('.pagination > a[!class]', -1)->href,
|
||||
$matches
|
||||
);
|
||||
foreach ($json as $json_item) {
|
||||
$item = array();
|
||||
|
||||
$lastpage = min($matches[1], ceil($max / 40));
|
||||
// Get image URI
|
||||
$uri = $json_item['urls']['regular'] . '.jpg'; // '.jpg' only for format hint
|
||||
$uri = str_replace('q=80', 'q=' . $quality, $uri);
|
||||
$uri = str_replace('w=1080', 'w=' . $width, $uri);
|
||||
$item['uri'] = $uri;
|
||||
|
||||
// Get title from description
|
||||
if (is_null($json_item['alt_description'])) {
|
||||
if (is_null($json_item['description'])) {
|
||||
$item['title'] = 'Unsplash picture from ' . $json_item['user']['name'];
|
||||
} else {
|
||||
$item['title'] = $json_item['description'];
|
||||
}
|
||||
} else {
|
||||
$item['title'] = $json_item['alt_description'];
|
||||
}
|
||||
|
||||
foreach($html->find('.photo') as $element) {
|
||||
$thumbnail = $element->find('img', 0);
|
||||
$thumbnail->src = str_replace('https://', 'http://', $thumbnail->src);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = str_replace(
|
||||
array('q=75', 'w=400'),
|
||||
array("q=$quality", "w=$width"),
|
||||
$thumbnail->src) . '.jpg'; // '.jpg' only for format hint
|
||||
|
||||
$item['timestamp'] = time();
|
||||
$item['title'] = $thumbnail->alt;
|
||||
$item['content'] = $item['title']
|
||||
$item['timestamp'] = time();
|
||||
$item['content'] = $item['title']
|
||||
. '<br><a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $thumbnail->src
|
||||
. $json_item['urls']['thumb']
|
||||
. '" /></a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
$num++;
|
||||
if ($num >= $max)
|
||||
break 2;
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
175
bridges/VimeoBridge.php
Normal file
175
bridges/VimeoBridge.php
Normal file
@@ -0,0 +1,175 @@
|
||||
<?php
|
||||
|
||||
class VimeoBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Vimeo Bridge';
|
||||
const URI = 'https://vimeo.com/';
|
||||
const DESCRIPTION = 'Returns search results from Vimeo';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'q' => array(
|
||||
'name' => 'Search Query',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Show results for',
|
||||
'type' => 'list',
|
||||
'defaultValue' => 'Videos',
|
||||
'values' => array(
|
||||
'Videos' => 'search',
|
||||
'On Demand' => 'search/ondemand',
|
||||
'People' => 'search/people',
|
||||
'Channels' => 'search/channels',
|
||||
'Groups' => 'search/groups'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getURI() {
|
||||
if(($query = $this->getInput('q'))
|
||||
&& ($type = $this->getInput('type'))) {
|
||||
return self::URI . $type . '/sort:latest?q=' . $query;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(),
|
||||
$header = array(),
|
||||
$opts = array(),
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
$target_charset = DEFAULT_TARGET_CHARSET,
|
||||
$stripRN = false, // We want to keep newline characters
|
||||
$defaultBRText = DEFAULT_BR_TEXT,
|
||||
$defaultSpanText = DEFAULT_SPAN_TEXT)
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$json = null; // Holds the JSON data
|
||||
|
||||
/**
|
||||
* Search results are included as JSON formatted string inside a script
|
||||
* tag that has the variable 'vimeo.config'. The data is condensed into
|
||||
* a single line of code, so we can just search for the newline.
|
||||
*
|
||||
* Everything after "vimeo.config = _extend((vimeo.config || {}), " is
|
||||
* the JSON formatted string.
|
||||
*/
|
||||
foreach($html->find('script') as $script) {
|
||||
foreach(explode("\n", $script) as $line) {
|
||||
$line = trim($line);
|
||||
|
||||
if(strpos($line, 'vimeo.config') !== 0)
|
||||
continue;
|
||||
|
||||
// 45 = strlen("vimeo.config = _extend((vimeo.config || {}), ");
|
||||
// 47 = 45 + 2, because we don't want the final ");"
|
||||
$json = json_decode(substr($line, 45, strlen($line) - 47));
|
||||
}
|
||||
}
|
||||
|
||||
if(is_null($json)) {
|
||||
returnClientError('No results for this query!');
|
||||
}
|
||||
|
||||
foreach($json->api->initial_json->data as $element) {
|
||||
switch($element->type) {
|
||||
case 'clip': $this->addClip($element); break;
|
||||
case 'ondemand': $this->addOnDemand($element); break;
|
||||
case 'people': $this->addPeople($element); break;
|
||||
case 'channel': $this->addChannel($element); break;
|
||||
case 'group': $this->addGroup($element); break;
|
||||
|
||||
default: returnServerError('Unknown type: ' . $element->type);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function addClip($element) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $element->clip->link;
|
||||
$item['title'] = $element->clip->name;
|
||||
$item['author'] = $element->clip->user->name;
|
||||
$item['timestamp'] = strtotime($element->clip->created_time);
|
||||
|
||||
$item['enclosures'] = array(
|
||||
end($element->clip->pictures->sizes)->link
|
||||
);
|
||||
|
||||
$item['content'] = "<img src={$item['enclosures'][0]} />";
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function addOnDemand($element) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $element->ondemand->link;
|
||||
$item['title'] = $element->ondemand->name;
|
||||
|
||||
// Only for films
|
||||
if(isset($element->ondemand->film))
|
||||
$item['timestamp'] = strtotime($element->ondemand->film->release_time);
|
||||
|
||||
$item['enclosures'] = array(
|
||||
end($element->ondemand->pictures->sizes)->link
|
||||
);
|
||||
|
||||
$item['content'] = "<img src={$item['enclosures'][0]} />";
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function addPeople($element) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $element->people->link;
|
||||
$item['title'] = $element->people->name;
|
||||
|
||||
$item['enclosures'] = array(
|
||||
end($element->people->pictures->sizes)->link
|
||||
);
|
||||
|
||||
$item['content'] = "<img src={$item['enclosures'][0]} />";
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function addChannel($element) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $element->channel->link;
|
||||
$item['title'] = $element->channel->name;
|
||||
|
||||
$item['enclosures'] = array(
|
||||
end($element->channel->pictures->sizes)->link
|
||||
);
|
||||
|
||||
$item['content'] = "<img src={$item['enclosures'][0]} />";
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function addGroup($element) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $element->group->link;
|
||||
$item['title'] = $element->group->name;
|
||||
|
||||
$item['enclosures'] = array(
|
||||
end($element->group->pictures->sizes)->link
|
||||
);
|
||||
|
||||
$item['content'] = "<img src={$item['enclosures'][0]} />";
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
@@ -13,6 +13,10 @@ class VkBridge extends BridgeAbstract
|
||||
'u' => array(
|
||||
'name' => 'Group or user name',
|
||||
'required' => true
|
||||
),
|
||||
'hide_reposts' => array(
|
||||
'name' => 'Hide reposts',
|
||||
'type' => 'checkbox',
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -48,7 +52,7 @@ class VkBridge extends BridgeAbstract
|
||||
$text_html = $this->getContents()
|
||||
or returnServerError('No results for group or user name "' . $this->getInput('u') . '".');
|
||||
|
||||
$text_html = iconv('windows-1251', 'utf-8', $text_html);
|
||||
$text_html = iconv('windows-1251', 'utf-8//ignore', $text_html);
|
||||
// makes album link generating work correctly
|
||||
$text_html = str_replace('"class="page_album_link">', '" class="page_album_link">', $text_html);
|
||||
$html = str_get_html($text_html);
|
||||
@@ -234,6 +238,9 @@ class VkBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
if (is_object($post->find('div.copy_quote', 0))) {
|
||||
if ($this->getInput('hide_reposts') === true) {
|
||||
continue;
|
||||
}
|
||||
$copy_quote = $post->find('div.copy_quote', 0);
|
||||
if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) {
|
||||
$copy_post_header->outertext = '';
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user