mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-16 05:24:08 +02:00
Compare commits
527 Commits
2018-03-10
...
2019-06-08
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
ae2c35c18a | ||
|
5b80bcaa04 | ||
|
5ea985164e | ||
|
696afa96d3 | ||
|
326a707739 | ||
|
1ac66b3fdc | ||
|
f450b2e118 | ||
|
688c950916 | ||
|
9d85b951f7 | ||
|
dac685b887 | ||
|
d37f0c14a0 | ||
|
b96c25a3af | ||
|
dc1b1b13cc | ||
|
e3588f62bd | ||
|
958ba815c7 | ||
|
3d24596a52 | ||
|
f9ed934c8c | ||
|
777c204838 | ||
|
ae40f7b388 | ||
|
473a62ed44 | ||
|
4c58768d4d | ||
|
ca9c2abb60 | ||
|
556a417dd6 | ||
|
51ee541d5a | ||
|
69cb65c1af | ||
|
29b187fc12 | ||
|
80f6a8b3d4 | ||
|
32d4da8b76 | ||
|
0063d2c376 | ||
|
11a39af35c | ||
|
f65a4076ba | ||
|
25593d9c18 | ||
|
394149b114 | ||
|
a29512deee | ||
|
e0db349a57 | ||
|
d532d0e0c4 | ||
|
434c12672f | ||
|
ab2e566ee1 | ||
|
493e76e4b9 | ||
|
37d882a8d5 | ||
|
bcd7bccc46 | ||
|
2def7a04a3 | ||
|
3c5b23daa6 | ||
|
ef6709c402 | ||
|
fc96e97d51 | ||
|
600f2290b6 | ||
|
245af35a60 | ||
|
ef4923ae5c | ||
|
18229b5c70 | ||
|
3160e62293 | ||
|
f81d1b0846 | ||
|
8801ac9e64 | ||
|
288d4de218 | ||
|
f3f33cabed | ||
|
3e45643418 | ||
|
719320e1a4 | ||
|
81ee15a161 | ||
|
988635dcf3 | ||
|
4095cad9b4 | ||
|
e7d3a006c8 | ||
|
ce65f51d91 | ||
|
4b22862295 | ||
|
185a773e74 | ||
|
10659dd453 | ||
|
6b2a45c1e8 | ||
|
6e4b6fa1cc | ||
|
0cad5f24e6 | ||
|
cb6ad7c077 | ||
|
4438807b26 | ||
|
6c1d861529 | ||
|
dc83962483 | ||
|
bb2329fa3a | ||
|
758f37b452 | ||
|
fb8a064e3a | ||
|
b00971b2c3 | ||
|
a07ead42a7 | ||
|
a11ade3442 | ||
|
3932e7b8ef | ||
|
5305c405f6 | ||
|
1c58c04271 | ||
|
89218f1da6 | ||
|
30e2b79c38 | ||
|
2184f523cd | ||
|
242b6953ed | ||
|
bdcb7a9829 | ||
|
f4b46e497e | ||
|
d5085a4116 | ||
|
d7cabfca54 | ||
|
de575982a1 | ||
|
3d301fc4ee | ||
|
263e8872ea | ||
|
6e9c188a72 | ||
|
49da67cb33 | ||
|
b4dbd191d0 | ||
|
e09f452426 | ||
|
7b261d1cc2 | ||
|
96a518c9e7 | ||
|
0d2ea9a677 | ||
|
66e82e46db | ||
|
54800fcc8d | ||
|
67004556e6 | ||
|
c6a7b9ac64 | ||
|
dbffbd4d4e | ||
|
1c17ffb5c4 | ||
|
326cfb21cf | ||
|
8ab1fb86a9 | ||
|
a9ec3d0d1f | ||
|
ac5bcb62ec | ||
|
f24ab8b51b | ||
|
4348119adf | ||
|
fd4124cda2 | ||
|
91f7405297 | ||
|
85685b7758 | ||
|
41d02554f3 | ||
|
c4550be812 | ||
|
b29ba5b973 | ||
|
254fe9212a | ||
|
3806895059 | ||
|
599d438a0d | ||
|
e5a6baab96 | ||
|
b47a30ecc1 | ||
|
860b36c1e3 | ||
|
3d475572c6 | ||
|
59f2d755fe | ||
|
d7c374bd8c | ||
|
6b6ab6486a | ||
|
6c4e239f64 | ||
|
88b0656954 | ||
|
66b11b8c41 | ||
|
1b34d9860e | ||
|
6e70d461e1 | ||
|
0a92b5d29b | ||
|
e3849f45ab | ||
|
3d9c4a3718 | ||
|
5f146a257e | ||
|
936688e08c | ||
|
4b5372638c | ||
|
6f4a8f4d03 | ||
|
39652bb050 | ||
|
fcac5b8b92 | ||
|
6f7b56cba8 | ||
|
86ac0a4866 | ||
|
4a99c6e630 | ||
|
e8442a3bf8 | ||
|
427688fd67 | ||
|
4a6b3654eb | ||
|
5f867c00b4 | ||
|
c15b25a07d | ||
|
c296e73c18 | ||
|
007ee4d858 | ||
|
dd95ec6200 | ||
|
d951000c23 | ||
|
51634a72e0 | ||
|
78c69b08f0 | ||
|
3bb3353897 | ||
|
e26d61ec0a | ||
|
a0490e3673 | ||
|
c63af2e7ad | ||
|
9379854f7a | ||
|
ecdac1b089 | ||
|
2104fc4d58 | ||
|
697d63bb96 | ||
|
2bb13169b4 | ||
|
4713fb6190 | ||
|
a08811f147 | ||
|
a935e310ff | ||
|
7e3787a185 | ||
|
039c032798 | ||
|
cb91d9cce8 | ||
|
bf91f106b4 | ||
|
0b2ede35cd | ||
|
5842bdfc83 | ||
|
68ee24d6bd | ||
|
104ae2298e | ||
|
7026684e34 | ||
|
0b792d77eb | ||
|
110b865a54 | ||
|
19a7f10160 | ||
|
42e25e7fc0 | ||
|
4b7fea5ebc | ||
|
95bd206e9d | ||
|
9910310652 | ||
|
12f0e5a360 | ||
|
81ba96ff94 | ||
|
984f0b24d0 | ||
|
2126db84ac | ||
|
4bf45df18e | ||
|
a88b148d20 | ||
|
f564925ba0 | ||
|
22e8f8b4aa | ||
|
bfae04d1fe | ||
|
723bd1150a | ||
|
53d2fbe3a5 | ||
|
3babd02658 | ||
|
3031fa406d | ||
|
85c34a0960 | ||
|
5deb86acff | ||
|
946e66e9df | ||
|
1a00dfa412 | ||
|
0f8443e1d3 | ||
|
7d474e5361 | ||
|
8c97953211 | ||
|
d987ceec73 | ||
|
392e3ff6c7 | ||
|
e295dc5a79 | ||
|
b9f6bc8197 | ||
|
9c1c0f2974 | ||
|
65da157fff | ||
|
5fe943562a | ||
|
c58331f74d | ||
|
145a46ae1d | ||
|
1a7a7bad98 | ||
|
27d6a22675 | ||
|
b55ec51e0e | ||
|
07b4c72d5d | ||
|
2e6cbd1ce7 | ||
|
2ac2f3dc66 | ||
|
e2dfea2b77 | ||
|
c4896c7791 | ||
|
7621784598 | ||
|
1cfe939927 | ||
|
c56f7abc2a | ||
|
e3030cbbfd | ||
|
953c6e1022 | ||
|
dbd44f64dd | ||
|
89ca42da54 | ||
|
b4b5340b7e | ||
|
a508dddb36 | ||
|
cb488d9d8c | ||
|
9820ad5c0f | ||
|
ea2d54523d | ||
|
87d218296e | ||
|
afd5ef0f1d | ||
|
30bc5179c2 | ||
|
7596be65f2 | ||
|
16f0ee7104 | ||
|
e0323f06cd | ||
|
717b0bdd9c | ||
|
62d737efe2 | ||
|
6fce03daa7 | ||
|
7561c0685d | ||
|
f48eac854f | ||
|
a87e7781b1 | ||
|
0dc761d6cf | ||
|
d14f8e3c83 | ||
|
b4aea21f71 | ||
|
c06a09fe99 | ||
|
704ad50607 | ||
|
d89c65d219 | ||
|
9a3c776096 | ||
|
85e8a67568 | ||
|
ee158468fa | ||
|
5779f641c0 | ||
|
b90bcee1fc | ||
|
996295e82f | ||
|
13bd7fe21b | ||
|
fcc9f9fd61 | ||
|
e1c4914b1c | ||
|
93e7ea9fea | ||
|
2d1b446bd1 | ||
|
1d451610d6 | ||
|
f853ffc07c | ||
|
e3a5a6a170 | ||
|
243e324efc | ||
|
ae58b1566e | ||
|
c044694b21 | ||
|
db24f55c86 | ||
|
eb30038d6b | ||
|
712a581ed6 | ||
|
d3df4b51b8 | ||
|
e6476a600d | ||
|
811e8d8c88 | ||
|
adc6f72e97 | ||
|
182153485c | ||
|
bf9946d1fc | ||
|
ec60752650 | ||
|
6688cf0c3b | ||
|
ae45a8cfee | ||
|
e34ef6cb4f | ||
|
5c92a736fa | ||
|
911bcfb246 | ||
|
efa550ef61 | ||
|
d5d7683ed3 | ||
|
fe94914eb5 | ||
|
622802e5d4 | ||
|
6da8daf1a3 | ||
|
654e502e84 | ||
|
c8ace9e3bd | ||
|
5722a6c139 | ||
|
458b826871 | ||
|
b397a42876 | ||
|
111c45d010 | ||
|
55b36b0455 | ||
|
de8cee6a1c | ||
|
123fce4394 | ||
|
a3f99c9c3f | ||
|
bf30ad127c | ||
|
37f84196b7 | ||
|
44764f7182 | ||
|
19f294d71d | ||
|
b0e33e4e01 | ||
|
558fa50a2a | ||
|
ffb8b82c73 | ||
|
422c125d8e | ||
|
059656c370 | ||
|
9fc1e97efe | ||
|
be3620acb7 | ||
|
16c0a61232 | ||
|
704a87ad97 | ||
|
c4cccfe0f3 | ||
|
d07deb0930 | ||
|
e7dab5d351 | ||
|
ad82d50bbd | ||
|
c305c1ded7 | ||
|
f14a5bd771 | ||
|
a20d5f9af0 | ||
|
ee28b124e0 | ||
|
7dee3a175a | ||
|
5fea9fc1f5 | ||
|
6bceb2b2db | ||
|
df81fa62d1 | ||
|
f8c6400373 | ||
|
de7622ebbf | ||
|
09c9d015b4 | ||
|
3a496e3b18 | ||
|
df58f5bbdb | ||
|
9d0452d11b | ||
|
f92ac49947 | ||
|
a574fa15ac | ||
|
8f9a385b4d | ||
|
53bdfa3bf0 | ||
|
53278b2eed | ||
|
5f3c55b808 | ||
|
fb79a67370 | ||
|
3c4e12ceba | ||
|
0d1923c52f | ||
|
ce896b4247 | ||
|
a4b2d88dbe | ||
|
65ec04ea98 | ||
|
afb4de318b | ||
|
43bb17f995 | ||
|
bae7a5879f | ||
|
bd760cbcee | ||
|
cd20b4476f | ||
|
d83f2f285b | ||
|
15e6d77569 | ||
|
f97d2ef254 | ||
|
91ae2a23d7 | ||
|
066ef1d7db | ||
|
4facbf32e3 | ||
|
6bd76af326 | ||
|
caa622ffec | ||
|
c4d489f018 | ||
|
6a98293fb3 | ||
|
d79630e3b8 | ||
|
1f2fe25471 | ||
|
87fc9e9156 | ||
|
c7b0c9fd31 | ||
|
fbf874cb29 | ||
|
049ee52fb5 | ||
|
3f41d0593a | ||
|
7126f5e838 | ||
|
ead7b2e8de | ||
|
0d80a19e84 | ||
|
42c699f474 | ||
|
2bc8daa101 | ||
|
bca79d3f88 | ||
|
90dc968fd1 | ||
|
da6b98851c | ||
|
71c29d4192 | ||
|
193ca87afa | ||
|
5ea79ac1fc | ||
|
937ea49271 | ||
|
95686b803c | ||
|
5087f5f79e | ||
|
4a5f190e0e | ||
|
01a2746715 | ||
|
f4a60c1777 | ||
|
1b08bce779 | ||
|
9fa74a36c6 | ||
|
7493e2b5b8 | ||
|
8e468a9ca7 | ||
|
50924b9213 | ||
|
4c5013bc82 | ||
|
7dc09db9ca | ||
|
d92da8f0f7 | ||
|
064ba456e8 | ||
|
8ac8e08abf | ||
|
c4f32c31a8 | ||
|
4369e077c2 | ||
|
1045850043 | ||
|
2d8f4dc3c5 | ||
|
779b638fb4 | ||
|
3ca59392c2 | ||
|
9b34b68180 | ||
|
79ebdc4b39 | ||
|
8770c87389 | ||
|
c1e3352218 | ||
|
00570ce1b4 | ||
|
df33dcff4e | ||
|
e60b5ab193 | ||
|
b0c7a62f74 | ||
|
57b15a089e | ||
|
4b7fbe4188 | ||
|
2390fb58b3 | ||
|
1e8d29f6ec | ||
|
644d13686c | ||
|
aa0ff1c9b1 | ||
|
539d9f1f06 | ||
|
5ece801ce7 | ||
|
4dcea6d9c9 | ||
|
d69e2521f1 | ||
|
7927d73719 | ||
|
0620f30ae0 | ||
|
795494cfce | ||
|
ba8542156c | ||
|
55f112e034 | ||
|
208fff801d | ||
|
3c9860de43 | ||
|
a16ec196c5 | ||
|
887fc7b037 | ||
|
1bd4a40f71 | ||
|
494169f959 | ||
|
178177e787 | ||
|
1cb83ccea3 | ||
|
c899399569 | ||
|
0f93370e92 | ||
|
45c3dcb636 | ||
|
ecfc220b10 | ||
|
4b3efed7ec | ||
|
bc28c5da8e | ||
|
5bd9c1611d | ||
|
6caca4946b | ||
|
ee78e7613f | ||
|
2df2623430 | ||
|
de5f850cdb | ||
|
ac6847045c | ||
|
df6da837dc | ||
|
41b7984a4e | ||
|
38c7e0272e | ||
|
29c690dbcd | ||
|
8ba817478b | ||
|
cacbe90102 | ||
|
cb91cd5d2f | ||
|
52dfa3fe76 |
14
.dockerignore
Normal file
14
.dockerignore
Normal file
@@ -0,0 +1,14 @@
|
||||
.git
|
||||
.gitattributes
|
||||
.github/*
|
||||
.travis.yml
|
||||
cache/*
|
||||
CONTRIBUTING.md
|
||||
DEBUG
|
||||
Dockerfile
|
||||
phpcompatibility.xml
|
||||
phpcs.xml
|
||||
phpcs.xml
|
||||
scalingo.json
|
||||
tests/*
|
||||
whitelist.txt
|
47
.gitattributes
vendored
47
.gitattributes
vendored
@@ -10,13 +10,40 @@
|
||||
*.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)
|
||||
## 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
|
||||
composer.json export-ignore
|
||||
composer.lock export-ignore
|
||||
## Heroku
|
||||
app.json export-ignore
|
||||
|
49
.github/CONTRIBUTING.md
vendored
Normal file
49
.github/CONTRIBUTING.md
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
### Pull request policy
|
||||
|
||||
* [Fix one issue per pull request](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#fix-one-issue-per-pull-request)
|
||||
* [Respect the coding style policy](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#respect-the-coding-style-policy)
|
||||
* [Properly name your commits](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#properly-name-your-commits)
|
||||
* When fixing a bridge (located in the `bridges` directory), write `[BridgeName] Feature` <br>(i.e. `[YoutubeBridge] Fix typo in video titles`).
|
||||
* When fixing other files, use `[FileName] Feature` <br>(i.e. `[index.php] Add multilingual support`).
|
||||
* When fixing a general problem that applies to multiple files, write `category: feature` <br>(i.e. `bridges: Fix various typos`).
|
||||
|
||||
Note that all pull-requests must pass all tests before they can be merged.
|
||||
|
||||
### Coding style
|
||||
|
||||
* [Whitespace](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace)
|
||||
* [Add a new line at the end of a file](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add a whitespace before a semicolon](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#add-a-new-line-at-the-end-of-a-file)
|
||||
* [Do not add whitespace at start or end of a file or end of a line](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitespace#do-not-add-whitespace-at-start-or-end-of-a-file-or-end-of-a-line)
|
||||
* [Indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation)
|
||||
* [Use tabs for indentation](https://github.com/RSS-Bridge/rss-bridge/wiki/Indentation#use-tabs-for-indentation)
|
||||
* [Maximum line length](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length)
|
||||
* [The maximum line length should not exceed 80 characters](https://github.com/RSS-Bridge/rss-bridge/wiki/Maximum-line-length#the-maximum-line-length-should-not-exceed-80-characters)
|
||||
* [Strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings)
|
||||
* [Whenever possible use single quoted strings](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#whenever-possible-use-single-quote-strings)
|
||||
* [Add spaces around the concatenation operator](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#add-spaces-around-the-concatenation-operator)
|
||||
* [Use a single string instead of concatenating](https://github.com/RSS-Bridge/rss-bridge/wiki/Strings#use-a-single-string-instead-of-concatenating)
|
||||
* [Constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants)
|
||||
* [Use UPPERCASE for constants](https://github.com/RSS-Bridge/rss-bridge/wiki/Constants#use-uppercase-for-constants)
|
||||
* [Keywords](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords)
|
||||
* [Use lowercase for `true`, `false` and `null`](https://github.com/RSS-Bridge/rss-bridge/wiki/Keywords#use-lowercase-for-true-false-and-null)
|
||||
* [Operators](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators)
|
||||
* [Operators must have a space around them](https://github.com/RSS-Bridge/rss-bridge/wiki/Operators#operators-must-have-a-space-around-them)
|
||||
* [Functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions)
|
||||
* [Parameters with default values must appear last in functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#parameters-with-default-values-must-appear-last-in-functions)
|
||||
* [Calling functions](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#calling-functions)
|
||||
* [Do not add spaces after opening or before closing bracket](https://github.com/RSS-Bridge/rss-bridge/wiki/Functions#do-not-add-spaces-after-opening-or-before-closing-bracket)
|
||||
* [Structures](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures)
|
||||
* [Structures must always be formatted as multi-line blocks](https://github.com/RSS-Bridge/rss-bridge/wiki/Structures#structures-must-always-be-formatted-as-multi-line-blocks)
|
||||
* [If-Statement](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement)
|
||||
* [Use `elseif` instead of `else if`](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#use-elseif-instead-of-else-if)
|
||||
* [Do not write empty statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-empty-statements)
|
||||
* [Do not write unconditional if-statements](https://github.com/RSS-Bridge/rss-bridge/wiki/if-Statement#do-not-write-unconditional-if-statements)
|
||||
* [Classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes)
|
||||
* [Use PascalCase for class names](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#use-pascalcase-for-class-names)
|
||||
* [Do not use final statements inside final classes](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-use-final-statements-inside-final-classes)
|
||||
* [Do not override methods to call their parent](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#do-not-override-methods-to-call-their-parent)
|
||||
* [abstract and final declarations MUST precede the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#abstract-and-final-declarations-must-precede-the-visibility-declaration)
|
||||
* [static declaration MUST come after the visibility declaration](https://github.com/RSS-Bridge/rss-bridge/wiki/Classes#static-declaration-must-come-after-the-visibility-declaration)
|
||||
* [Casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting)
|
||||
* [Do not add spaces when casting](https://github.com/RSS-Bridge/rss-bridge/wiki/Casting#do-not-add-spaces-when-casting)
|
64
.github/ISSUE_TEMPLATE/bridge-request.md
vendored
Normal file
64
.github/ISSUE_TEMPLATE/bridge-request.md
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
---
|
||||
name: Bridge request
|
||||
about: Use this template for requesting a new bridge
|
||||
title: Bridge request for ...
|
||||
labels: Bridge-Request
|
||||
assignees: ''
|
||||
|
||||
---
|
||||
|
||||
# Bridge request
|
||||
|
||||
<!--
|
||||
This is a bridge request. Start by adding a descriptive title (i.e. `Bridge request for GitHub`). Use the "Preview" button to see a preview of your request. Make sure your request is complete before submitting!
|
||||
|
||||
Notice: This comment is only visible to you while you work on your request. Please do not remove any of the lines in the template (you may add your own outside the "<!--" and "- ->" lines!)
|
||||
-->
|
||||
|
||||
## General information
|
||||
|
||||
<!--
|
||||
Please describe what you expect from the bridge. Whenever possible provide sample links and screenshots (you can just paste them here) to express your expectations and help others understand your request. If possible, mark relevant areas in your screenshot. Use the following questions for reference:
|
||||
-->
|
||||
|
||||
- _Host URI for the bridge_ (i.e. `https://github.com`):
|
||||
|
||||
- Which information would you like to see?
|
||||
|
||||
|
||||
|
||||
- How should the information be displayed/formatted?
|
||||
|
||||
|
||||
|
||||
- Which of the following parameters do you expect?
|
||||
|
||||
- [X] Title
|
||||
- [X] URI (link to the original article)
|
||||
- [ ] Author
|
||||
- [ ] Timestamp
|
||||
- [X] Content (the content of the article)
|
||||
- [ ] Enclosures (pictures, videos, etc...)
|
||||
- [ ] Categories (categories, tags, etc...)
|
||||
|
||||
## Options
|
||||
|
||||
<!--Select options from the list below. Add your own option if one is missing:-->
|
||||
|
||||
- [ ] Limit number of returned items
|
||||
- _Default limit_: 5
|
||||
- [ ] Load full articles
|
||||
- _Cache articles_ (articles are stored in a local cache on first request): yes
|
||||
- _Cache timeout_ (max = 24 hours): 24 hours
|
||||
- [X] Balance requests (RSS-Bridge uses cached versions to reduce bandwith usage)
|
||||
- _Timeout_ (default = 5 minutes, max = 24 hours): 5 minutes
|
||||
|
||||
<!--Be aware that some options might not be available for your specific request due to technical limitations!-->
|
||||
|
||||
<!--
|
||||
## Additional notes
|
||||
|
||||
Keep in mind that opening a request does not guarantee the bridge being implemented! That depends entirely on the interest and time of others to make the bridge for you.
|
||||
|
||||
You can also implement your own bridge (with support of the community if needed). Find more information in the [RSS-Bridge Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/For-developers) developer section.
|
||||
-->
|
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.
|
13
.gitignore
vendored
13
.gitignore
vendored
@@ -227,8 +227,19 @@ pip-log.txt
|
||||
/cache
|
||||
/whitelist.txt
|
||||
DEBUG
|
||||
config.ini.php
|
||||
|
||||
######################
|
||||
## VisualStudioCode ##
|
||||
######################
|
||||
.vscode/*
|
||||
.vscode/*
|
||||
|
||||
#Builder
|
||||
.buildconfig
|
||||
|
||||
#Auth
|
||||
.htaccess
|
||||
.htpasswd
|
||||
|
||||
#Crawler
|
||||
robots.txt
|
||||
|
38
.travis.yml
38
.travis.yml
@@ -1,24 +1,46 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
install:
|
||||
- pear channel-update pear.php.net
|
||||
- pear install PHP_CodeSniffer
|
||||
- composer global require dealerdirect/phpcodesniffer-composer-installer;
|
||||
- composer global require phpcompatibility/php-compatibility;
|
||||
- if [[ "$PHPUNIT" ]]; then
|
||||
composer global require phpunit/phpunit ^$PHPUNIT;
|
||||
fi
|
||||
|
||||
script:
|
||||
- phpenv rehash
|
||||
- phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
|
||||
# Run PHP_CodeSniffer on all versions
|
||||
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
# Check PHP compatibility for the lowest and highest supported version
|
||||
- if [[ $TRAVIS_PHP_VERSION == "5.6" || $TRAVIS_PHP_VERSION == "7.3" ]]; then
|
||||
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
|
||||
fi
|
||||
# Run unit tests on highest major version
|
||||
- if [[ ${TRAVIS_PHP_VERSION:0:1} == "7" ]]; then
|
||||
~/.config/composer/vendor/bin/phpunit --configuration=phpunit.xml --include-path=lib/;
|
||||
fi
|
||||
|
||||
php:
|
||||
- 7.3
|
||||
|
||||
env:
|
||||
- PHPUNIT=6
|
||||
- PHPUNIT=7
|
||||
- PHPUNIT=8
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
include:
|
||||
- php: 5.6
|
||||
env: PHPUNIT=
|
||||
- php: 7.0
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
- php: 7.1
|
||||
- php: 7.2
|
||||
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
- php: 7.3
|
||||
env: PHPUNIT=7
|
||||
- php: 7.3
|
||||
env: PHPUNIT=8
|
||||
|
263
CHANGELOG.md
263
CHANGELOG.md
@@ -1,263 +0,0 @@
|
||||
rss-bridge Changelog
|
||||
===
|
||||
|
||||
RSS-Bridge 2017-08-19
|
||||
==
|
||||
|
||||
## General changes
|
||||
* whitelist: Do case-insensitive whitelist matching
|
||||
* [FeedExpander] Fix Serialization of 'SimpleXMLElement' is not allowed
|
||||
* [FeedExpander] Remove whitespace from source content
|
||||
* [index] Add GET parameter 'q' for search queries
|
||||
- **Example**: You can now add `&q=Twitter` to load into the search field
|
||||
* [index] Check permissions for cache folder and whitelist file
|
||||
* [index] Show bridge options when loading with URL fragment
|
||||
- **Example**: You can now add `#bridge-Twitter` to load the card with all
|
||||
parameters visible
|
||||
* [style] Center search cursor and hide placeholder
|
||||
* [validation] Fix error on undefined optional numeric value
|
||||
|
||||
## Modified bridges
|
||||
* [DanbooruBridge] Allow descendant classes to override tag collection
|
||||
* [DribbbleBridge] Add dribble bridge listing last dribble popular shots (#558)
|
||||
* [FacebookBridge] Fix & in URLs
|
||||
* [GelbooruBridge] Fix bridge not getting tags correctly
|
||||
* [GoComicsBridge] Fix for page structure changes (#568)
|
||||
* [LeBonCoinBridge] Fix bridge is marked executable
|
||||
* [LWNprevBridge] Fix everchanging url
|
||||
* [YoutubeBridge] Fix error on certain keywords
|
||||
* [YoutubeBridge] Fix issues loading playlists
|
||||
|
||||
## Removed bridges
|
||||
* VineBridge
|
||||
|
||||
RSS-Bridge 2017-08-03
|
||||
==
|
||||
|
||||
## Important changes
|
||||
* RSS-Bridge now has [contribution guidelines](CONTRIBUTING.md)
|
||||
* [phpcs rules](phpcs.xml) follow the [contribution guidelines](CONTRIBUTING.md)
|
||||
|
||||
## General changes
|
||||
* Added a search bar to make searching for bridges easier
|
||||
* Added user friendly error page for when a bridge fails
|
||||
* Added caching of extraInfos (name, uri)
|
||||
* Added an indicator to warn for bridges using HTTP instead of HTTPS
|
||||
* Various bug fixes and improvements
|
||||
|
||||
## Modified bridges
|
||||
* AllocineFRBridge] Update Faux Raccord link
|
||||
* [DanbooruBridge] Fix broken URI
|
||||
* [DuckDuckGoBridge] Disable DuckDuckGo redirects so that the links returned are correct.
|
||||
* [FacebookBridge] Add option to hide posts with facebook videos
|
||||
* [FacebookBridge] Add requester languages to HTTP header
|
||||
* [FacebookBridge] Handle summary posts
|
||||
* [FacebookBridge] Replace 'novideo' with 'media_type'
|
||||
* [FilterBridge] Initial implementation of basic title permit and block
|
||||
* [FlickrTagBridge] Fix and improve bridge by using the FlickrExploreBridge approach
|
||||
* [GooglePlusPostBridge] Autofix user names
|
||||
* [GooglePlusPostBridge] Fix bridge implementation
|
||||
* [GooglePlusPostBridge] Fix content loading
|
||||
* [InstagramBridge] Add option to filter for videos and pictures
|
||||
* [LWNprevBridge] full rewrite
|
||||
* [MangareaderBridge] Fix double forward slashes
|
||||
* [NasaApodBridge] Use HTTPS instead of HTTP
|
||||
* [PinterestBridge] Fix checkbox not working
|
||||
* [PinterestBridge] Fix implementation after DOM changes
|
||||
* [RTBFBridge] Update URI
|
||||
* [SexactuBridge] Fix URI and timestamp
|
||||
* [SexactuBridge] Use most modern version of bridge api and cached pages (#504)
|
||||
* [ShanaprojectBridge] Don't throw error if timestamp is missing
|
||||
* [TwitterBridge] Add option to hide retweets
|
||||
* [TwitterBridge] Avoid empty content caused by new login policy
|
||||
* [TwitterBridge] Fix double slashes in URI
|
||||
* [TwitterBridge] Fix missing spaces
|
||||
* [TwitterBridge] Fix title includes anchors in plaintext format
|
||||
* [TwitterBridge] ignore promoted tweets
|
||||
* [TwitterBridge] Optimize returned image sizes
|
||||
* [TwitterBridge] Show quotes and pictures
|
||||
* [WebfailBridge] Properly handle gifs (DOM changed)
|
||||
* [YoutubeBridge] Improve readability of feed contents
|
||||
* [YoutubeBridge] Improve URL handling in video descriptions
|
||||
|
||||
## New bridges
|
||||
* AmazonBridge
|
||||
* DiceBridge
|
||||
* EtsyBridge
|
||||
* FB2Bridge
|
||||
* FilterBridge
|
||||
* FlickrBridge
|
||||
* GithubSearchBridge
|
||||
* GoComicsBridge
|
||||
* KATBridge
|
||||
* KernelBugTrackerBridge
|
||||
* MixCloudBridge
|
||||
* MoinMoinBridge
|
||||
* RainbowSixSiegeBridge
|
||||
* SteamBridge
|
||||
* TheTVDBBridge
|
||||
* Torrent9Bridge
|
||||
* UsbekEtRicaBridge
|
||||
* WikiLeaksBridge
|
||||
* WordPressPluginUpdateBridge
|
||||
|
||||
Alpha 0.2
|
||||
===
|
||||
|
||||
## Important changes
|
||||
* RSS-Bridge has been [UNLICENSED](UNLICENSE)
|
||||
* RSS-Bridge is now a community-managed project on [GitHub](https://github.com/rss-bridge/rss-bridge)
|
||||
* RSS-Bridge now has a [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
|
||||
* RSS-Bridge now supports [Travis-CI](https://travis-ci.org)
|
||||
|
||||
## General changes
|
||||
* Added [CHANGELOG](CHANGELOG.md) (this file)
|
||||
* Added [PHP Simple HTML DOM Parser](http://simplehtmldom.sourceforge.net) to [vendor](vendor/simplehtmldom/)
|
||||
* Added cache purging function (cache will be force-purged after 24 hours or as defined by bridge)
|
||||
* Added new format [MrssFormat](formats/MrssFormat.php)
|
||||
* Added parameter `author` - for display of the feed author name - to all formats
|
||||
* Added new abstraction of the BridgeInterface:
|
||||
- [FeedExpander](https://github.com/RSS-Bridge/rss-bridge/wiki/Bridge-API)
|
||||
* Added optional support for proxy usage on each individual bridge
|
||||
* Added support for [custom bridge parameter](https://github.com/RSS-Bridge/rss-bridge/wiki/BridgeAbstract#format-specifications) (text, number, list, checkbox)
|
||||
* Changed design of the welcome screen
|
||||
* Changed design of HtmlFormat
|
||||
* Changed behavior of debug mode:
|
||||
- Enable debug mode by placing a file called "DEBUG" in the root folder
|
||||
- Debug mode automatically disables cache file loading
|
||||
* Changed implementation of bridges - see [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
|
||||
- Changed comment-style metadata to constants
|
||||
- Added support for multiple utilizations per bridge
|
||||
- Changed the parameter loading algorithm to be loaded by RSS-Bridge core
|
||||
* Improved checks for PHP version, configuration and extensions
|
||||
* Many bug fixes
|
||||
|
||||
## Modified Bridges
|
||||
* FlickrExploreBridge
|
||||
* GoogleSearchBridge
|
||||
* TwitterBridge
|
||||
|
||||
## New Bridges
|
||||
* ABCTabsBridge
|
||||
* AcrimedBridge
|
||||
* AllocineFRBridge
|
||||
* AnimeUltimeBridge
|
||||
* Arte7Bridge
|
||||
* AskfmBridge
|
||||
* BandcampBridge
|
||||
* BastaBridge
|
||||
* BlaguesDeMerdeBridge
|
||||
* BooruprojectBridge
|
||||
* CADBridge
|
||||
* CNETBridge
|
||||
* CastorusBridge
|
||||
* CollegeDeFranceBridge
|
||||
* CommonDreamsBridge
|
||||
* CopieDoubleBridge
|
||||
* CourrierInternationalBridge
|
||||
* CpasbienBridge
|
||||
* CryptomeBridge
|
||||
* DailymotionBridge
|
||||
* DanbooruBridge
|
||||
* DansTonChatBridge
|
||||
* DauphineLibereBridge
|
||||
* DemoBridge
|
||||
* DeveloppezDotComBridge
|
||||
* DilbertBridge
|
||||
* DollbooruBridge
|
||||
* DuckDuckGoBridge
|
||||
* EZTVBridge
|
||||
* EliteDangerousGalnetBridge
|
||||
* ElsevierBridge
|
||||
* EstCeQuonMetEnProdBridge
|
||||
* FacebookBridge
|
||||
* FierPandaBridge
|
||||
* FlickrTagBridge
|
||||
* FootitoBridge
|
||||
* FourchanBridge
|
||||
* FuturaSciencesBridge
|
||||
* GBAtempBridge
|
||||
* GelbooruBridge
|
||||
* GiphyBridge
|
||||
* GithubIssueBridge
|
||||
* GizmodoBridge
|
||||
* GooglePlusPostBridge
|
||||
* HDWallpapersBridge
|
||||
* HentaiHavenBridge
|
||||
* IdenticaBridge
|
||||
* InstagramBridge
|
||||
* IsoHuntBridge
|
||||
* JapanExpoBridge
|
||||
* KonachanBridge
|
||||
* KoreusBridge
|
||||
* KununuBridge
|
||||
* LWNprevBridge
|
||||
* LeBonCoinBridge
|
||||
* LegifranceJOBridge
|
||||
* LeMondeInformatiqueBridge
|
||||
* LesJoiesDuCodeBridge
|
||||
* LichessBridge
|
||||
* LinkedInCompanyBridge
|
||||
* LolibooruBridge
|
||||
* MangareaderBridge
|
||||
* MilbooruBridge
|
||||
* MoebooruBridge
|
||||
* MondeDiploBridge
|
||||
* MsnMondeBridge
|
||||
* MspabooruBridge
|
||||
* NasaApodBridge
|
||||
* NeuviemeArtBridge
|
||||
* NextInpactBridge
|
||||
* NextgovBridge
|
||||
* NiceMatinBridge
|
||||
* NovelUpdatesBridge
|
||||
* OpenClassroomsBridge
|
||||
* ParuVenduImmoBridge
|
||||
* PickyWallpapersBridge
|
||||
* PinterestBridge
|
||||
* PlanetLibreBridge
|
||||
* RTBFBridge
|
||||
* ReadComicsBridge
|
||||
* Releases3DSBridge
|
||||
* ReporterreBridge
|
||||
* Rue89Bridge
|
||||
* Rule34Bridge
|
||||
* Rule34pahealBridge
|
||||
* SafebooruBridge
|
||||
* SakugabooruBridge
|
||||
* ScmbBridge
|
||||
* ScoopItBridge
|
||||
* SensCritiqueBridge
|
||||
* SexactuBridge
|
||||
* ShanaprojectBridge
|
||||
* Shimmie2Bridge
|
||||
* SoundcloudBridge
|
||||
* StripeAPIChangeLogBridge
|
||||
* SuperbWallpapersBridge
|
||||
* T411Bridge
|
||||
* TagBoardBridge
|
||||
* TbibBridge
|
||||
* TheCodingLoveBridge
|
||||
* TheHackerNewsBridge
|
||||
* ThePirateBayBridge
|
||||
* UnsplashBridge
|
||||
* ViadeoCompanyBridge
|
||||
* VineBridge
|
||||
* VkBridge
|
||||
* WallpaperStopBridge
|
||||
* WebfailBridge
|
||||
* WeLiveSecurityBridge
|
||||
* WhydBridge
|
||||
* WikipediaBridge
|
||||
* WordPressBridge
|
||||
* WorldOfTanksBridge
|
||||
* XbooruBridge
|
||||
* YandereBridge
|
||||
* YoutubeBridge
|
||||
* ZDNetBridge
|
||||
|
||||
Alpha 0.1
|
||||
===
|
||||
* First tagged version.
|
||||
* Includes refactoring.
|
||||
* Unstable.
|
@@ -1,47 +0,0 @@
|
||||
### Pull request policy
|
||||
Fix one issue per pull request.
|
||||
Squash commits before opening a pull request.
|
||||
Respect the coding style policy.
|
||||
Name your PR like the following :
|
||||
|
||||
* When correcting a single bridge, use `[BridgeName] Feature`.
|
||||
* When fixing a problem in a specific file, use `[FileName] Feature`.
|
||||
* When fixing a general problem, use `category : feature`.
|
||||
|
||||
Note that all pull-requests should pass the unit tests before they can be merged.
|
||||
|
||||
### Coding style
|
||||
|
||||
Use `camelCase` for variables and methods.
|
||||
Use `UPPERCASE` for constants.
|
||||
Use `PascalCase` for class names. When creating a bridge, your class and PHP file should be named `MyImplementationBridge`.
|
||||
Use tabs for indentation.
|
||||
Add an empty line at the end of your file.
|
||||
|
||||
Use `''` to encapsulate strings, including in arrays.
|
||||
Prefer lines shorter than 80 chars, no line longer than 120 chars.
|
||||
PHP constants should be in lower case (`true, false, null`...)
|
||||
|
||||
|
||||
* Add spaces between the logical operator and your expressions (not needed for the `!` operator).
|
||||
* Use `||` and `&&` instead of `or` and `and`.
|
||||
* Add space between your condition and the opening bracket/closing bracket.
|
||||
* Don't put a space between `if` and your bracket.
|
||||
* Use `elseif` instead of `else if`.
|
||||
* Add new lines in your conditions if they are containing more than one line.
|
||||
* Example :
|
||||
|
||||
```PHP
|
||||
if($a == true && $b) {
|
||||
print($a);
|
||||
} else if(!$b) {
|
||||
|
||||
$a = !$a;
|
||||
$b = $b >> $a;
|
||||
print($b);
|
||||
|
||||
} else {
|
||||
print($b);
|
||||
}
|
||||
```
|
||||
|
11
Dockerfile
Normal file
11
Dockerfile
Normal file
@@ -0,0 +1,11 @@
|
||||
FROM php:7-apache
|
||||
|
||||
ENV APACHE_DOCUMENT_ROOT=/app
|
||||
|
||||
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
|
||||
&& apt-get --yes update && apt-get --yes install libxml2-dev \
|
||||
&& 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/
|
249
README.md
249
README.md
@@ -1,10 +1,12 @@
|
||||
rss-bridge
|
||||
===
|
||||
[](UNLICENSE) [](https://travis-ci.org/RSS-Bridge/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 ATOM feeds for websites which don't have one.
|
||||
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.
|
||||
|
||||
Supported sites/pages (main)
|
||||
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
|
||||
|
||||
Supported sites/pages (examples)
|
||||
===
|
||||
|
||||
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
|
||||
@@ -25,106 +27,211 @@ Supported sites/pages (main)
|
||||
* `Wikipedia`: highlighted articles from [Wikipedia](https://wikipedia.org/) in English, German, French or Esperanto
|
||||
* `YouTube` : YouTube user channel, playlist or search
|
||||
|
||||
Plus [many other bridges](bridges/) to enable, thanks to the community
|
||||
And [many more](bridges/), thanks to the community!
|
||||
|
||||
Output format
|
||||
===
|
||||
Output format can take several forms:
|
||||
|
||||
* `Atom` : ATOM Feed, for use in RSS/Feed readers
|
||||
* `Html` : Simple html page.
|
||||
* `Json` : Json, for consumption by other applications.
|
||||
* `Mrss` : MRSS Feed, for use in RSS/Feed readers
|
||||
* `Plaintext` : raw text (php object, as returned by print_r)
|
||||
|
||||
RSS-Bridge is capable of producing several output formats:
|
||||
|
||||
* `Atom` : Atom feed, for use in feed readers
|
||||
* `Html` : Simple HTML page
|
||||
* `Json` : JSON, for consumption by other applications
|
||||
* `Mrss` : MRSS feed, for use in feed readers
|
||||
* `Plaintext` : Raw text, for consumption by other applications
|
||||
|
||||
You can extend RSS-Bridge with your own format, using the [Format API](https://github.com/RSS-Bridge/rss-bridge/wiki/Format-API)!
|
||||
|
||||
Screenshot
|
||||
===
|
||||
|
||||
Welcome screen:
|
||||
|
||||

|
||||
|
||||
RSS-Bridge hashtag (#rss-bridge) search on Twitter, in ATOM format (as displayed by Firefox):
|
||||
|
||||
***
|
||||
|
||||
RSS-Bridge hashtag (#rss-bridge) search on Twitter, in Atom format (as displayed by Firefox):
|
||||
|
||||

|
||||
|
||||
|
||||
Requirements
|
||||
===
|
||||
|
||||
* PHP 5.6, e.g. `AddHandler application/x-httpd-php56 .php` in `.htaccess`
|
||||
* `openssl` extension enabled in PHP config (`php.ini`)
|
||||
* `allow_url_fopen=1` in `php.ini`
|
||||
RSS-Bridge requires PHP 5.6 or higher with following extensions enabled:
|
||||
|
||||
Enabling/Disabling bridges
|
||||
- [`openssl`](https://secure.php.net/manual/en/book.openssl.php)
|
||||
- [`libxml`](https://secure.php.net/manual/en/book.libxml.php)
|
||||
- [`mbstring`](https://secure.php.net/manual/en/book.mbstring.php)
|
||||
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
|
||||
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
|
||||
- [`json`](https://secure.php.net/manual/en/book.json.php)
|
||||
- [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
|
||||
|
||||
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
|
||||
|
||||
Enable / Disable bridges
|
||||
===
|
||||
|
||||
By default, the script creates `whitelist.txt` and adds the main bridges (see above). `whitelist.txt` is ignored by git, you can edit it:
|
||||
* to enable extra bridges (one bridge per line)
|
||||
* to disable main bridges (remove the line)
|
||||
* to enable all bridges (just one wildcard `*` as file content)
|
||||
RSS-Bridge allows you to take full control over which bridges are displayed to the user. That way you can host your own RSS-Bridge service with your favorite collection of bridges!
|
||||
|
||||
New bridges are disabled by default, so make sure to check regularly what's new and whitelist what you want!
|
||||
Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting)
|
||||
|
||||
**Notice**: By default RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
|
||||
|
||||
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://heroku.com/deploy)
|
||||
|
||||
Getting involved
|
||||
===
|
||||
|
||||
There are many ways for you to getting involved with RSS-Bridge. Here are a few things:
|
||||
|
||||
- Share RSS-Bridge with your friends (Twitter, Facebook, ..._you name it_...)
|
||||
- Report broken bridges or bugs by opening [Issues](https://github.com/RSS-Bridge/rss-bridge/issues) on GitHub
|
||||
- Request new features or suggest ideas (via [Issues](https://github.com/RSS-Bridge/rss-bridge/issues))
|
||||
- Discuss bugs, features, ideas or [issues](https://github.com/RSS-Bridge/rss-bridge/issues)
|
||||
- Add new bridges or improve the API
|
||||
- Improve the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki)
|
||||
- Host an instance of RSS-Bridge for your personal use or make it available to the community :sparkling_heart:
|
||||
|
||||
Authors
|
||||
===
|
||||
We are RSS Bridge Community, a group of developers continuing the project initiated by sebsauvage, webmaster of [sebsauvage.net](http://sebsauvage.net), author of [Shaarli](http://sebsauvage.net/wiki/doku.php?id=php:shaarli) and [ZeroBin](http://sebsauvage.net/wiki/doku.php?id=php:zerobin).
|
||||
|
||||
Patch/contributors :
|
||||
We are RSS-Bridge community, a group of developers continuing the project initiated by sebsauvage, webmaster of [sebsauvage.net](http://sebsauvage.net), author of [Shaarli](http://sebsauvage.net/wiki/doku.php?id=php:shaarli) and [ZeroBin](http://sebsauvage.net/wiki/doku.php?id=php:zerobin).
|
||||
|
||||
* Yves ASTIER ([Draeli](https://github.com/Draeli)) : PHP optimizations, fixes, dynamic brigde/format list with all stuff behind and extend cache system. Mail : contact /at\ yves-astier.com
|
||||
* [Mitsukarenai](https://github.com/Mitsukarenai) : Initial inspiration, collaborator
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [BoboTiG](https://github.com/BoboTiG)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [qwertygc](https://github.com/qwertygc)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [Anadrark](https://github.com/Anadrark])
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [Polopollo](https://github.com/Polopollo)
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [kranack](https://github.com/kranack)
|
||||
* [logmanoriginal](https://github.com/logmanoriginal)
|
||||
* [polo2ro](https://github.com/polo2ro)
|
||||
* [Riduidel](https://github.com/Riduidel)
|
||||
* [superbaillot.net](http://superbaillot.net/)
|
||||
* [vinzv](https://github.com/vinzv)
|
||||
* [teromene](https://github.com/teromene)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [nyutag](https://github.com/nyutag)
|
||||
* [ORelio](https://github.com/ORelio)
|
||||
* [Pitchoule](https://github.com/Pitchoule)
|
||||
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [erwang](https://github.com/erwang)
|
||||
* [gsurrel](https://github.com/gsurrel)
|
||||
* [kraoc](https://github.com/kraoc)
|
||||
* [lagaisse](https://github.com/lagaisse)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [mro](https://github.com/mro)
|
||||
**Contributors** (sorted alphabetically):
|
||||
<!--
|
||||
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)
|
||||
* [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)
|
||||
* [disk0x](https://github.com/disk0x)
|
||||
* [DJCrashdummy](https://github.com/DJCrashdummy)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [DnAp](https://github.com/DnAp)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
|
||||
* [em92](https://github.com/em92)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [fulmeek](https://github.com/fulmeek)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [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)
|
||||
* [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)
|
||||
* [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
|
||||
===
|
||||
Code is [Public Domain](UNLICENSE).
|
||||
|
||||
Including `PHP Simple HTML DOM Parser` under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
The source code for RSS-Bridge is [Public Domain](UNLICENSE).
|
||||
|
||||
RSS-Bridge uses third party libraries with their own license:
|
||||
|
||||
* [`PHP Simple HTML DOM Parser`](http://simplehtmldom.sourceforge.net/) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
* [`php-urljoin`](https://github.com/fluffy-critter/php-urljoin) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
|
||||
Technical notes
|
||||
===
|
||||
* There is a cache so that source services won't ban you even if you hammer the rss-bridge with requests. Each bridge can have a different duration for the cache. The `cache` subdirectory will be automatically created and cached objects older than 24 hours get purged.
|
||||
* To implement a new Bridge, [follow the specifications](https://github.com/RSS-Bridge/rss-bridge/wiki/Bridge-API) and take a look at existing Bridges for examples.
|
||||
* To enable debug mode (disabling cache and enabling error reporting), create an empty file named `DEBUG` in the root directory (next to `index.php`).
|
||||
* For more information refer to the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki)
|
||||
|
||||
* RSS-Bridge uses caching to prevent services from banning your server for repeatedly updating feeds. The specific cache duration can be different between bridges. Cached files are deleted automatically after 24 hours.
|
||||
* You can implement your own bridge, [following these instructions](https://github.com/RSS-Bridge/rss-bridge/wiki/Bridge-API).
|
||||
* You can enable debug mode to disable caching. Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Debug-mode)
|
||||
|
||||
Rant
|
||||
===
|
||||
@@ -133,10 +240,10 @@ Rant
|
||||
|
||||
Your catchword is "share", but you don't want us to share. You want to keep us within your walled gardens. That's why you've been removing RSS links from webpages, hiding them deep on your website, or removed feeds entirely, replacing it with crippled or demented proprietary API. **FUCK YOU.**
|
||||
|
||||
You're not social when you hamper sharing by removing feeds. You're happy to have customers creating content for your ecosystem, but you don't want this content out - a content you do not even own. Google Takeout is just a gimmick. We want our data to flow, we want RSS or ATOM feeds.
|
||||
You're not social when you hamper sharing by removing feeds. You're happy to have customers creating content for your ecosystem, but you don't want this content out - a content you do not even own. Google Takeout is just a gimmick. We want our data to flow, we want RSS or Atom feeds.
|
||||
|
||||
We want to share with friends, using open protocols: RSS, ATOM, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
|
||||
We want to share with friends, using open protocols: RSS, Atom, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
|
||||
|
||||
We are rebuilding bridges you have wilfully destroyed.
|
||||
|
||||
Get your shit together: Put RSS/ATOM back in.
|
||||
Get your shit together: Put RSS/Atom back in.
|
||||
|
50
actions/DetectAction.php
Normal file
50
actions/DetectAction.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
class DetectAction extends ActionAbstract {
|
||||
public function execute() {
|
||||
$targetURL = $this->userData['url']
|
||||
or returnClientError('You must specify a url!');
|
||||
|
||||
$format = $this->userData['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
|
||||
if(!Bridge::isWhitelisted($bridgeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams = $bridge->detectParameters($targetURL);
|
||||
|
||||
if(is_null($bridgeParams)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams['bridge'] = $bridgeName;
|
||||
$bridgeParams['format'] = $format;
|
||||
|
||||
header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
|
||||
die();
|
||||
|
||||
}
|
||||
|
||||
returnClientError('No bridge found for given URL: ' . $targetURL);
|
||||
}
|
||||
}
|
234
actions/DisplayAction.php
Normal file
234
actions/DisplayAction.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
class DisplayAction extends ActionAbstract {
|
||||
public function execute() {
|
||||
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
|
||||
|
||||
$format = $this->userData['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
// DEPRECATED: 'nameFormat' scheme is replaced by 'name' in format parameter values
|
||||
// this is to keep compatibility until futher complete removal
|
||||
if(($pos = strpos($format, 'Format')) === (strlen($format) - strlen('Format'))) {
|
||||
$format = substr($format, 0, $pos);
|
||||
}
|
||||
|
||||
// whitelist control
|
||||
if(!Bridge::isWhitelisted($bridge)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
|
||||
// Data retrieval
|
||||
$bridge = Bridge::create($bridge);
|
||||
|
||||
$noproxy = array_key_exists('_noproxy', $this->userData)
|
||||
&& filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
|
||||
define('NOPROXY', true);
|
||||
}
|
||||
|
||||
// Cache timeout
|
||||
$cache_timeout = -1;
|
||||
if(array_key_exists('_cache_timeout', $this->userData)) {
|
||||
|
||||
if(!CUSTOM_CACHE_TIMEOUT) {
|
||||
unset($this->userData['_cache_timeout']);
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData);
|
||||
header('Location: ' . $uri, true, 301);
|
||||
die();
|
||||
}
|
||||
|
||||
$cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT);
|
||||
|
||||
} else {
|
||||
$cache_timeout = $bridge->getCacheTimeout();
|
||||
}
|
||||
|
||||
// Remove parameters that don't concern bridges
|
||||
$bridge_params = array_diff_key(
|
||||
$this->userData,
|
||||
array_fill_keys(
|
||||
array(
|
||||
'action',
|
||||
'bridge',
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
// Remove parameters that don't concern caches
|
||||
$cache_params = array_diff_key(
|
||||
$this->userData,
|
||||
array_fill_keys(
|
||||
array(
|
||||
'action',
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope('');
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setKey($cache_params);
|
||||
|
||||
$items = array();
|
||||
$infos = array();
|
||||
$mtime = $cache->getTime();
|
||||
|
||||
if($mtime !== false
|
||||
&& (time() - $cache_timeout < $mtime)
|
||||
&& !Debug::isEnabled()) { // Load cached data
|
||||
|
||||
// Send "Not Modified" response if client supports it
|
||||
// Implementation based on https://stackoverflow.com/a/10847262
|
||||
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
||||
|
||||
if($mtime <= $stime) { // Cached data is older or same
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
$cached = $cache->loadData();
|
||||
|
||||
if(isset($cached['items']) && isset($cached['extraInfos'])) {
|
||||
foreach($cached['items'] as $item) {
|
||||
$items[] = new \FeedItem($item);
|
||||
}
|
||||
|
||||
$infos = $cached['extraInfos'];
|
||||
}
|
||||
|
||||
} else { // Collect new data
|
||||
|
||||
try {
|
||||
$bridge->setDatas($bridge_params);
|
||||
$bridge->collectData();
|
||||
|
||||
$items = $bridge->getItems();
|
||||
|
||||
// Transform "legacy" items to FeedItems if necessary.
|
||||
// Remove this code when support for "legacy" items ends!
|
||||
if(isset($items[0]) && is_array($items[0])) {
|
||||
$feedItems = array();
|
||||
|
||||
foreach($items as $item) {
|
||||
$feedItems[] = new \FeedItem($item);
|
||||
}
|
||||
|
||||
$items = $feedItems;
|
||||
}
|
||||
|
||||
$infos = array(
|
||||
'name' => $bridge->getName(),
|
||||
'uri' => $bridge->getURI(),
|
||||
'icon' => $bridge->getIcon()
|
||||
);
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = new \FeedItem();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
// Error 0 is a special case (i.e. "trying to get property of non-object")
|
||||
if($e->getCode() === 0) {
|
||||
$item->setTitle(
|
||||
'Bridge encountered an unexpected situation! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
} else {
|
||||
$item->setTitle(
|
||||
'Bridge returned error '
|
||||
. $e->getCode()
|
||||
. '! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
}
|
||||
|
||||
$item->setURI(
|
||||
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?'
|
||||
. http_build_query($this->userData)
|
||||
);
|
||||
|
||||
$item->setTimestamp(time());
|
||||
$item->setContent(buildBridgeException($e, $bridge));
|
||||
|
||||
$items[] = $item;
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
|
||||
$item = new \FeedItem();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
$item->setURI(
|
||||
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?'
|
||||
. http_build_query($this->userData)
|
||||
);
|
||||
|
||||
$item->setTitle(
|
||||
'Bridge returned error '
|
||||
. $e->getCode()
|
||||
. '! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
$item->setTimestamp(time());
|
||||
$item->setContent(buildBridgeException($e, $bridge));
|
||||
|
||||
$items[] = $item;
|
||||
}
|
||||
|
||||
// Store data in cache
|
||||
$cache->saveData(array(
|
||||
'items' => array_map(function($i){ return $i->toArray(); }, $items),
|
||||
'extraInfos' => $infos
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
// Data transformation
|
||||
try {
|
||||
$format = Format::create($format);
|
||||
$format->setItems($items);
|
||||
$format->setExtraInfos($infos);
|
||||
$format->setLastModified($cache->getTime());
|
||||
$format->display();
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
header('Content-Type: text/html', true, $e->getCode());
|
||||
die(buildTransformException($e, $bridge));
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
header('Content-Type: text/html', true, $e->getCode());
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
}
|
||||
}
|
53
actions/ListAction.php
Normal file
53
actions/ListAction.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
class ListAction extends ActionAbstract {
|
||||
public function execute() {
|
||||
$list = new StdClass();
|
||||
$list->bridges = array();
|
||||
$list->total = 0;
|
||||
|
||||
foreach(Bridge::getBridgeNames() as $bridgeName) {
|
||||
|
||||
$bridge = Bridge::create($bridgeName);
|
||||
|
||||
if($bridge === false) { // Broken bridge, show as inactive
|
||||
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => 'inactive'
|
||||
);
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
$status = Bridge::isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => $status,
|
||||
'uri' => $bridge->getURI(),
|
||||
'name' => $bridge->getName(),
|
||||
'icon' => $bridge->getIcon(),
|
||||
'parameters' => $bridge->getParameters(),
|
||||
'maintainer' => $bridge->getMaintainer(),
|
||||
'description' => $bridge->getDescription()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
$list->total = count($list->bridges);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($list, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
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"]
|
||||
}
|
||||
|
@@ -8,7 +8,7 @@ class ABCTabsBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM(static::URI.'tablatures/nouveautes.html')
|
||||
$html = getSimpleHTMLDOM(static::URI . 'tablatures/nouveautes.html')
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
$table = $html->find('table#myTable', 0)->children(1);
|
||||
|
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';
|
||||
}
|
||||
}
|
@@ -21,5 +21,4 @@ class AcrimedBridge extends FeedExpander {
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -10,7 +10,6 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
'category' => array(
|
||||
'name' => 'category',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'exampleValue' => 'Faux Raccord',
|
||||
'title' => 'Select your category',
|
||||
'values' => array(
|
||||
@@ -45,7 +44,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
return self::NAME . ' : '
|
||||
.array_search(
|
||||
. array_search(
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
@@ -83,5 +82,4 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@ class AmazonBridge extends BridgeAbstract {
|
||||
'sort' => array(
|
||||
'name' => 'Sort by',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'Relevance' => 'relevanceblender',
|
||||
'Price: Low to High' => 'price-asc-rank',
|
||||
@@ -29,7 +28,6 @@ class AmazonBridge extends BridgeAbstract {
|
||||
'tld' => array(
|
||||
'name' => 'Country',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'Australia' => 'com.au',
|
||||
'Brazil' => 'com.br',
|
||||
@@ -52,7 +50,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
|
||||
return 'Amazon.'.$this->getInput('tld').': '.$this->getInput('q');
|
||||
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
@@ -60,8 +58,8 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$uri = 'https://www.amazon.'.$this->getInput('tld').'/';
|
||||
$uri .= 's/?field-keywords='.urlencode($this->getInput('q')).'&sort='.$this->getInput('sort');
|
||||
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
|
||||
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Amazon.');
|
||||
@@ -72,6 +70,9 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
// Title
|
||||
$title = $element->find('h2', 0);
|
||||
if (is_null($title)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);
|
||||
|
||||
@@ -86,7 +87,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
$price = $element->find('span.s-price', 0);
|
||||
$price = ($price) ? $price->innertext : '';
|
||||
|
||||
$item['content'] = '<img src="'.$image->getAttribute('src').'" /><br />'.$price;
|
||||
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />' . $price;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
186
bridges/AmazonPriceTrackerBridge.php
Normal file
186
bridges/AmazonPriceTrackerBridge.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?php
|
||||
|
||||
class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const NAME = 'Amazon Price Tracker';
|
||||
const URI = 'https://www.amazon.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Tracks price for a single product on Amazon';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'asin' => array(
|
||||
'name' => 'ASIN',
|
||||
'required' => true,
|
||||
'exampleValue' => 'B071GB1VMQ',
|
||||
// https://stackoverflow.com/a/12827734
|
||||
'pattern' => 'B[\dA-Z]{9}|\d{9}(X|\d)',
|
||||
),
|
||||
'tld' => array(
|
||||
'name' => 'Country',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Australia' => 'com.au',
|
||||
'Brazil' => 'com.br',
|
||||
'Canada' => 'ca',
|
||||
'China' => 'cn',
|
||||
'France' => 'fr',
|
||||
'Germany' => 'de',
|
||||
'India' => 'in',
|
||||
'Italy' => 'it',
|
||||
'Japan' => 'co.jp',
|
||||
'Mexico' => 'com.mx',
|
||||
'Netherlands' => 'nl',
|
||||
'Spain' => 'es',
|
||||
'United Kingdom' => 'co.uk',
|
||||
'United States' => 'com',
|
||||
),
|
||||
'defaultValue' => 'com',
|
||||
),
|
||||
));
|
||||
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
* Generates domain name given a amazon TLD
|
||||
*/
|
||||
private function getDomainName() {
|
||||
return 'https://www.amazon.' . $this->getInput('tld');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates URI for a Amazon product page
|
||||
*/
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('asin'))) {
|
||||
return $this->getDomainName() . '/dp/' . $this->getInput('asin') . '/';
|
||||
}
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrapes the product title from the html page
|
||||
* returns the default title if scraping fails
|
||||
*/
|
||||
private function getTitle($html) {
|
||||
$titleTag = $html->find('#productTitle', 0);
|
||||
|
||||
if (!$titleTag) {
|
||||
return $this->getDefaultTitle();
|
||||
} else {
|
||||
return trim(html_entity_decode($titleTag->innertext, ENT_QUOTES));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Title used by the feed if none could be found
|
||||
*/
|
||||
private function getDefaultTitle() {
|
||||
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns name for the feed
|
||||
* Uses title (already scraped) if it has one
|
||||
*/
|
||||
public function getName() {
|
||||
if (isset($this->title)) {
|
||||
return $this->title;
|
||||
} else {
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
private function parseDynamicImage($attribute) {
|
||||
$json = json_decode(html_entity_decode($attribute), true);
|
||||
|
||||
if ($json and count($json) > 0) {
|
||||
return array_keys($json)[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a generated image tag for the product
|
||||
*/
|
||||
private function getImage($html) {
|
||||
$imageSrc = $html->find('#main-image-container img', 0);
|
||||
|
||||
if ($imageSrc) {
|
||||
$hiresImage = $imageSrc->getAttribute('data-old-hires');
|
||||
$dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image');
|
||||
$image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute);
|
||||
}
|
||||
$image = $image ?: 'https://placekitten.com/200/300';
|
||||
|
||||
return <<<EOT
|
||||
<img width="300" style="max-width:300;max-height:300" src="$image" alt="{$this->title}" />
|
||||
EOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return \simple_html_dom object
|
||||
* for the entire html of the product page
|
||||
*/
|
||||
private function getHtml() {
|
||||
$uri = $this->getURI();
|
||||
|
||||
return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.');
|
||||
}
|
||||
|
||||
private function scrapePriceFromMetrics($html) {
|
||||
$asinData = $html->find('#cerberus-data-metrics', 0);
|
||||
|
||||
// <div id="cerberus-data-metrics" style="display: none;"
|
||||
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
|
||||
// data-asin-currency-code="USD" data-substitute-count="-1" ... />
|
||||
if ($asinData) {
|
||||
return [
|
||||
'price' => $asinData->getAttribute('data-asin-price'),
|
||||
'currency' => $asinData->getAttribute('data-asin-currency-code'),
|
||||
'shipping' => $asinData->getAttribute('data-asin-shipping')
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function scrapePriceGeneric($html) {
|
||||
$priceDiv = $html->find('span.offer-price', 0) ?: $html->find('.a-color-price', 0);
|
||||
|
||||
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
|
||||
|
||||
if (count($matches) === 3) {
|
||||
return [
|
||||
'price' => $matches[2],
|
||||
'currency' => $matches[1],
|
||||
'shipping' => '0'
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape method for Amazon product page
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function collectData() {
|
||||
$html = $this->getHtml();
|
||||
$this->title = $this->getTitle($html);
|
||||
$imageTag = $this->getImage($html);
|
||||
|
||||
$data = $this->scrapePriceFromMetrics($html) ?: $this->scrapePriceGeneric($html);
|
||||
|
||||
$item = array(
|
||||
'title' => $this->title,
|
||||
'uri' => $this->getURI(),
|
||||
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
|
||||
);
|
||||
|
||||
if ($data['shipping'] !== '0') {
|
||||
$item['content'] .= "<br>Shipping: {$data['shipping']} {$data['currency']}</br>";
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
207
bridges/AnidexBridge.php
Normal file
207
bridges/AnidexBridge.php
Normal file
@@ -0,0 +1,207 @@
|
||||
<?php
|
||||
class AnidexBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Anidex';
|
||||
const URI = 'https://anidex.info/';
|
||||
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'id' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All categories' => '0',
|
||||
'Anime' => '1,2,3',
|
||||
'Anime - Sub' => '1',
|
||||
'Anime - Raw' => '2',
|
||||
'Anime - Dub' => '3',
|
||||
'Live Action' => '4,5',
|
||||
'Live Action - Sub' => '4',
|
||||
'Live Action - Raw' => '5',
|
||||
'Light Novel' => '6',
|
||||
'Manga' => '7,8',
|
||||
'Manga - Translated' => '7',
|
||||
'Manga - Raw' => '8',
|
||||
'Music' => '9,10,11',
|
||||
'Music - Lossy' => '9',
|
||||
'Music - Lossless' => '10',
|
||||
'Music - Video' => '11',
|
||||
'Games' => '12',
|
||||
'Applications' => '13',
|
||||
'Pictures' => '14',
|
||||
'Adult Video' => '15',
|
||||
'Other' => '16'
|
||||
)
|
||||
),
|
||||
'lang_id' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All languages' => '0',
|
||||
'English' => '1',
|
||||
'Japanese' => '2',
|
||||
'Polish' => '3',
|
||||
'Serbo-Croatian' => '4',
|
||||
'Dutch' => '5',
|
||||
'Italian' => '6',
|
||||
'Russian' => '7',
|
||||
'German' => '8',
|
||||
'Hungarian' => '9',
|
||||
'French' => '10',
|
||||
'Finnish' => '11',
|
||||
'Vietnamese' => '12',
|
||||
'Greek' => '13',
|
||||
'Bulgarian' => '14',
|
||||
'Spanish (Spain)' => '15',
|
||||
'Portuguese (Brazil)' => '16',
|
||||
'Portuguese (Portugal)' => '17',
|
||||
'Swedish' => '18',
|
||||
'Arabic' => '19',
|
||||
'Danish' => '20',
|
||||
'Chinese (Simplified)' => '21',
|
||||
'Bengali' => '22',
|
||||
'Romanian' => '23',
|
||||
'Czech' => '24',
|
||||
'Mongolian' => '25',
|
||||
'Turkish' => '26',
|
||||
'Indonesian' => '27',
|
||||
'Korean' => '28',
|
||||
'Spanish (LATAM)' => '29',
|
||||
'Persian' => '30',
|
||||
'Malaysian' => '31'
|
||||
)
|
||||
),
|
||||
'group_id' => array(
|
||||
'name' => 'Group ID',
|
||||
'type' => 'number'
|
||||
),
|
||||
'r' => array(
|
||||
'name' => 'Hide Remakes',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'b' => array(
|
||||
'name' => 'Only Batches',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'a' => array(
|
||||
'name' => 'Only Authorized',
|
||||
'type' => 'checkbox'
|
||||
),
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'description' => 'Keyword(s)',
|
||||
'type' => 'text'
|
||||
),
|
||||
'h' => array(
|
||||
'name' => 'Adult content',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'No filter' => '0',
|
||||
'Hide +18' => '1',
|
||||
'Only +18' => '2'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
// Build Search URL from user-provided parameters
|
||||
$search_url = self::URI . '?s=upload_timestamp&o=desc';
|
||||
foreach (array('id', 'lang_id', 'group_id') as $param_name) {
|
||||
$param = $this->getInput($param_name);
|
||||
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
|
||||
$search_url .= '&' . $param_name . '=' . $param;
|
||||
}
|
||||
}
|
||||
foreach (array('r', 'b', 'a') as $param_name) {
|
||||
$param = $this->getInput($param_name);
|
||||
if (!empty($param) && boolval($param)) {
|
||||
$search_url .= '&' . $param_name . '=1';
|
||||
}
|
||||
}
|
||||
$query = $this->getInput('q');
|
||||
if (!empty($query)) {
|
||||
$search_url .= '&q=' . urlencode($query);
|
||||
}
|
||||
$opt = array();
|
||||
$h = $this->getInput('h');
|
||||
if (!empty($h) && intval($h) != 0 && ctype_digit($h)) {
|
||||
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
|
||||
}
|
||||
|
||||
// Retrieve torrent listing from search results, which does not contain torrent description
|
||||
$html = getSimpleHTMLDOM($search_url, array(), $opt)
|
||||
or returnServerError('Could not request Anidex: ' . $search_url);
|
||||
$links = $html->find('a');
|
||||
$results = array();
|
||||
foreach ($links as $link)
|
||||
if (strpos($link->href, '/torrent/') === 0 && !in_array($link->href, $results))
|
||||
$results[] = $link->href;
|
||||
if (empty($results) && empty($this->getInput('q')))
|
||||
returnServerError('No results from Anidex: ' . $search_url);
|
||||
|
||||
//Process each item individually
|
||||
foreach ($results as $element) {
|
||||
|
||||
//Limit total amount of requests
|
||||
if(count($this->items) >= 20) {
|
||||
break;
|
||||
}
|
||||
|
||||
$torrent_id = str_replace('/torrent/', '', $element);
|
||||
|
||||
//Ignore entries without valid torrent ID
|
||||
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
|
||||
|
||||
//Retrieve data for this torrent ID
|
||||
$item_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
|
||||
$item_desc = $item_html->find('div.panel-body', 0);
|
||||
$item_author = trim($item_html->find('span.fa-user', 0)->parent()->plaintext);
|
||||
$item_date = strtotime(trim($item_html->find('span.fa-clock', 0)->parent()->plaintext));
|
||||
$item_image = $this->getURI() . 'images/user_logos/default.png';
|
||||
|
||||
//Check for description-less torrent andn optionally extract image
|
||||
$desc_title_found = false;
|
||||
foreach ($item_html->find('h3.panel-title') as $h3) {
|
||||
if (strpos($h3, 'Description') !== false) {
|
||||
$desc_title_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($desc_title_found) {
|
||||
//Retrieve image for thumbnail or generic logo fallback
|
||||
foreach ($item_desc->find('img') as $img) {
|
||||
if (strpos($img->src, 'prez') === false) {
|
||||
$item_image = $img->src;
|
||||
break;
|
||||
}
|
||||
}
|
||||
$item_desc = trim($item_desc->innertext);
|
||||
} else {
|
||||
$item_desc = '<em>No description.</em>';
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
$item['uri'] = $item_uri;
|
||||
$item['title'] = $item_title;
|
||||
$item['author'] = $item_author;
|
||||
$item['timestamp'] = $item_date;
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_desc;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
$element = null;
|
||||
}
|
||||
$results = null;
|
||||
}
|
||||
}
|
@@ -5,7 +5,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
const NAME = 'Anime-Ultime';
|
||||
const URI = 'http://www.anime-ultime.net/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const DESCRIPTION = 'Returns the 10 newest releases posted on Anime-Ultime';
|
||||
const DESCRIPTION = 'Returns the newest releases posted on Anime-Ultime.';
|
||||
const PARAMETERS = array( array(
|
||||
'type' => array(
|
||||
'name' => 'Type',
|
||||
@@ -65,6 +65,13 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
$item_link_element = $release->find('td', 0)->find('a', 0);
|
||||
$item_uri = self::URI . $item_link_element->href;
|
||||
$item_name = html_entity_decode($item_link_element->plaintext);
|
||||
|
||||
$item_image = self::URI . substr(
|
||||
$item_link_element->onmouseover,
|
||||
37,
|
||||
strpos($item_link_element->onmouseover, ' ', 37) - 37
|
||||
);
|
||||
|
||||
$item_episode = html_entity_decode(
|
||||
str_pad(
|
||||
$release->find('td', 1)->plaintext,
|
||||
@@ -79,8 +86,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
if(!empty($item_uri)) {
|
||||
|
||||
// Retrieve description from description page and
|
||||
// convert relative image src info absolute image src
|
||||
// Retrieve description from description page
|
||||
$html_item = getContents($item_uri)
|
||||
or returnServerError('Could not request Anime-Ultime: ' . $item_uri);
|
||||
$item_description = substr(
|
||||
@@ -91,10 +97,9 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
0,
|
||||
strpos($item_description, '<div id="table">')
|
||||
);
|
||||
$item_description = str_replace(
|
||||
'src="images', 'src="' . self::URI . 'images',
|
||||
$item_description
|
||||
);
|
||||
|
||||
// Convert relative image src into absolute image src, remove line breaks
|
||||
$item_description = defaultLinkTo($item_description, self::URI);
|
||||
$item_description = str_replace("\r", '', $item_description);
|
||||
$item_description = str_replace("\n", '', $item_description);
|
||||
$item_description = utf8_encode($item_description);
|
||||
@@ -105,6 +110,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
$item['title'] = $item_name . ' ' . $item_type . ' ' . $item_episode;
|
||||
$item['author'] = $item_fansub;
|
||||
$item['timestamp'] = $item_date;
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_description;
|
||||
$this->items[] = $item;
|
||||
$processedOK++;
|
||||
@@ -131,5 +137,4 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
62
bridges/AppleMusicBridge.php
Normal file
62
bridges/AppleMusicBridge.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
class AppleMusicBridge extends BridgeAbstract {
|
||||
const NAME = 'Apple Music';
|
||||
const URI = 'https://www.apple.com';
|
||||
const DESCRIPTION = 'Fetches the latest releases from an artist';
|
||||
const MAINTAINER = 'Limero';
|
||||
const PARAMETERS = [[
|
||||
'url' => [
|
||||
'name' => 'Artist URL',
|
||||
'exampleValue' => 'https://itunes.apple.com/us/artist/dunderpatrullen/329796274',
|
||||
'required' => true,
|
||||
],
|
||||
'imgSize' => [
|
||||
'name' => 'Image size for thumbnails (in px)',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 512,
|
||||
'required' => true,
|
||||
]
|
||||
]];
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request: ' . $url);
|
||||
|
||||
$imgSize = $this->getInput('imgSize');
|
||||
|
||||
// Grab the json data from the page
|
||||
$html = $html->find('script[id=shoebox-ember-data-store]', 0);
|
||||
$html = strstr($html, '{');
|
||||
$html = substr($html, 0, -9);
|
||||
$json = json_decode($html);
|
||||
|
||||
// Loop through each object
|
||||
foreach ($json->included as $obj) {
|
||||
if ($obj->type === 'lockup/album') {
|
||||
$this->items[] = [
|
||||
'title' => $obj->attributes->artistName . ' - ' . $obj->attributes->name,
|
||||
'uri' => $obj->attributes->url,
|
||||
'timestamp' => $obj->attributes->releaseDate,
|
||||
'enclosures' => $obj->relationships->artwork->data->id,
|
||||
];
|
||||
} elseif ($obj->type === 'image') {
|
||||
$images[$obj->id] = $obj->attributes->url;
|
||||
}
|
||||
}
|
||||
|
||||
// Add the images to each item
|
||||
foreach ($this->items as &$item) {
|
||||
$item['enclosures'] = [
|
||||
str_replace('{w}x{h}bb.{f}', $imgSize . 'x0w.jpg', $images[$item['enclosures']]),
|
||||
];
|
||||
}
|
||||
|
||||
// Sort the order to put the latest albums first
|
||||
usort($this->items, function($a, $b){
|
||||
return $a['timestamp'] < $b['timestamp'];
|
||||
});
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -28,6 +28,13 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
)
|
||||
)
|
||||
),
|
||||
'Collection (Français)' => array(
|
||||
'colfr' => array(
|
||||
'name' => 'Collection id',
|
||||
'required' => true,
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/fr/videos/RC-014095/blow-up/'
|
||||
)
|
||||
),
|
||||
'Catégorie (Allemand)' => array(
|
||||
'catde' => array(
|
||||
'type' => 'list',
|
||||
@@ -45,6 +52,13 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
'Sonstiges' => 'AUT'
|
||||
)
|
||||
)
|
||||
),
|
||||
'Collection (Allemand)' => array(
|
||||
'colde' => array(
|
||||
'name' => 'Collection id',
|
||||
'required' => true,
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -54,23 +68,31 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
$category = $this->getInput('catfr');
|
||||
$lang = 'fr';
|
||||
break;
|
||||
case 'Collection (Français)':
|
||||
$lang = 'fr';
|
||||
$collectionId = $this->getInput('colfr');
|
||||
break;
|
||||
case 'Catégorie (Allemand)':
|
||||
$category = $this->getInput('catde');
|
||||
$lang = 'de';
|
||||
break;
|
||||
case 'Collection (Allemand)':
|
||||
$lang = 'de';
|
||||
$collectionId = $this->getInput('colde');
|
||||
break;
|
||||
}
|
||||
|
||||
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=10&language='
|
||||
. $lang
|
||||
. ($category != null ? '&category.code=' . $category : '');
|
||||
. ($category != null ? '&category.code=' . $category : '')
|
||||
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
|
||||
|
||||
$context = array(
|
||||
'http' => array(
|
||||
'header' => 'Authorization: Bearer '. self::API_TOKEN
|
||||
)
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
);
|
||||
|
||||
$input = getContents($url, false, stream_context_create($context)) 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) {
|
||||
@@ -98,5 +120,4 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
72
bridges/AsahiShimbunAJWBridge.php
Normal file
72
bridges/AsahiShimbunAJWBridge.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
class AsahiShimbunAJWBridge extends BridgeAbstract {
|
||||
const NAME = 'Asahi Shimbun AJW';
|
||||
const BASE_URI = 'http://www.asahi.com';
|
||||
const URI = self::BASE_URI . '/ajw/';
|
||||
const DESCRIPTION = 'Asahi Shimbun - Asia & Japan Watch';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'section' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Section',
|
||||
'values' => array(
|
||||
'Japan » Social Affairs' => 'japan/social',
|
||||
'Japan » People' => 'japan/people',
|
||||
'Japan » 3/11 Disaster' => 'japan/0311disaster',
|
||||
'Japan » Sci & Tech' => 'japan/sci_tech',
|
||||
'Politics' => 'politics',
|
||||
'Business' => 'business',
|
||||
'Culture » Style' => 'culture/style',
|
||||
'Culture » Movies' => 'culture/movies',
|
||||
'Culture » Manga & Anime' => 'culture/manga_anime',
|
||||
'Asia » China' => 'asia/china',
|
||||
'Asia » Korean Peninsula' => 'asia/korean_peninsula',
|
||||
'Asia » Around Asia' => 'asia/around_asia',
|
||||
'Opinion » Editorial' => 'opinion/editorial',
|
||||
'Opinion » Vox Populi' => 'opinion/vox',
|
||||
),
|
||||
'defaultValue' => 'Politics',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function getSectionURI($section) {
|
||||
return self::getURI() . $section . '/';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')))
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach($html->find('#MainInner li a') as $element) {
|
||||
if ($element->parent()->class == 'HeadlineTopImage-S') {
|
||||
Debug::log('Skip Headline, it is repeated below');
|
||||
continue;
|
||||
}
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = self::BASE_URI . $element->href;
|
||||
$e_lead = $element->find('span.Lead', 0);
|
||||
if ($e_lead) {
|
||||
$item['content'] = $e_lead->innertext;
|
||||
$e_lead->outertext = '';
|
||||
} else {
|
||||
$item['content'] = $element->innertext;
|
||||
}
|
||||
$e_date = $element->find('span.EnDate', 0);
|
||||
if ($e_date) {
|
||||
$item['timestamp'] = strtotime($e_date->innertext);
|
||||
$e_date->outertext = '';
|
||||
}
|
||||
$e_video = $element->find('span.EnVideo', 0);
|
||||
if ($e_video) {
|
||||
$e_video->outertext = '';
|
||||
$element->innertext = "VIDEO: $element->innertext";
|
||||
}
|
||||
$item['title'] = $element->innertext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'az5he6ch';
|
||||
const MAINTAINER = 'az5he6ch, logmanoriginal';
|
||||
const NAME = 'Ask.fm Answers';
|
||||
const URI = 'https://ask.fm/';
|
||||
const CACHE_TIMEOUT = 300; //5 min
|
||||
@@ -19,39 +19,39 @@ class AskfmBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
|
||||
foreach($html->find('div.streamItem-answer') as $element) {
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('article.streamItem-answer') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $element->find('a.streamItemsAge', 0)->href;
|
||||
$question = trim($element->find('h1.streamItemContent-question', 0)->innertext);
|
||||
$item['uri'] = $element->find('a.streamItem_meta', 0)->href;
|
||||
$question = trim($element->find('header.streamItem_header', 0)->innertext);
|
||||
|
||||
$item['title'] = trim(
|
||||
htmlspecialchars_decode($element->find('h1.streamItemContent-question', 0)->plaintext,
|
||||
htmlspecialchars_decode($element->find('header.streamItem_header', 0)->plaintext,
|
||||
ENT_QUOTES
|
||||
)
|
||||
);
|
||||
|
||||
$answer = trim($element->find('p.streamItemContent-answer', 0)->innertext);
|
||||
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
|
||||
|
||||
// Doesn't work, DOM parser doesn't seem to like data-hint, dunno why
|
||||
#$item['update'] = $element->find('a.streamitemsage',0)->data-hint;
|
||||
$answer = trim($element->find('div.streamItem_content', 0)->innertext);
|
||||
|
||||
// This probably should be cleaned up, especially for YouTube embeds
|
||||
$visual = $element->find('div.streamItemContent-visual', 0)->innertext;
|
||||
//Fix tracking links, also doesn't work
|
||||
if($visual = $element->find('div.streamItem_visual', 0)) {
|
||||
$visual = $visual->innertext;
|
||||
}
|
||||
|
||||
// Fix tracking links, also doesn't work
|
||||
foreach($element->find('a') as $link) {
|
||||
if(strpos($link->href, 'l.ask.fm') !== false) {
|
||||
|
||||
// Too slow
|
||||
#$link->href = str_replace('#_=_', '', get_headers($link->href, 1)['Location']);
|
||||
|
||||
$link->href = $link->plaintext;
|
||||
}
|
||||
}
|
||||
|
||||
$content = '<p>' . $question . '</p><p>' . $answer . '</p><p>' . $visual . '</p>';
|
||||
// Fix relative links without breaking // scheme used by YouTube stuff
|
||||
$content = preg_replace('#href="\/(?!\/)#', 'href="' . self::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$item['content'] = '<p>' . $question
|
||||
. '</p><p>' . $answer
|
||||
. '</p><p>' . $visual . '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
@@ -66,7 +66,7 @@ class AskfmBridge extends BridgeAbstract {
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u')) . '/answers/more?page=0';
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
197
bridges/AutoJMBridge.php
Normal file
197
bridges/AutoJMBridge.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
|
||||
class AutoJMBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'AutoJM';
|
||||
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 du modèle',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
|
||||
'exampleValue' => 'achat-voitures-neuves-peugeot-nouvelle-308-5p'
|
||||
),
|
||||
'isDispo' => array(
|
||||
'name' => 'Disponibilité',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'-' => '',
|
||||
'En stock' => 1,
|
||||
'Sur commande' => 0
|
||||
),
|
||||
'title' => 'Critère de disponibilité'
|
||||
),
|
||||
'energy' => array(
|
||||
'name' => 'Carburant',
|
||||
'type' => 'list',
|
||||
'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 . 'favicon.ico';
|
||||
}
|
||||
|
||||
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[isDispo]' => $this->getInput('isDispo'),
|
||||
'form[energy]' => $this->getInput('energy'),
|
||||
'form[transmission]' => $this->getInput('transmission'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[priceMin]' => $this->getInput('priceMin'),
|
||||
'form[_token]' => $this->token
|
||||
);
|
||||
|
||||
// Set the Form request content type
|
||||
$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('h2');
|
||||
foreach ($list as $finish) {
|
||||
$finish_name = $finish->plaintext;
|
||||
$motorizations = $finish->next_sibling()->find('li');
|
||||
foreach ($motorizations as $element) {
|
||||
$image = $element->find('div[class=block-product-image]', 0)->{'data-ga-banner'};
|
||||
$serie = $element->find('span[class=model]', 0)->plaintext;
|
||||
$url = self::URI . substr($element->find('a', 0)->href, 1);
|
||||
if ($element->find('span[class*=block-product-nbModel]', 0) != null) {
|
||||
$availability = 'En Stock';
|
||||
} else {
|
||||
$availability = 'Sur commande';
|
||||
}
|
||||
$discount_html = $element->find('span[class*=tag--promo]', 0);
|
||||
if ($discount_html != null) {
|
||||
$discount = $discount_html->plaintext;
|
||||
} else {
|
||||
$discount = 'inconnue';
|
||||
}
|
||||
$price = $element->find('span[class=price red h1]', 0)->plaintext;
|
||||
$item = array();
|
||||
$item['title'] = $finish_name . ' ' . $serie;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
|
||||
. $finish_name . ' ' . $serie . '</p>';
|
||||
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
|
||||
$item['content'] .= '<li>Série : ' . $serie . '</li>';
|
||||
$item['content'] .= '<li>Remise : ' . $discount . '</li>';
|
||||
$item['content'] .= '<li>Prix : ' . $price . '</li></ul>';
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
265
bridges/BAEBridge.php
Normal file
265
bridges/BAEBridge.php
Normal file
@@ -0,0 +1,265 @@
|
||||
<?php
|
||||
class BAEBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Bourse Aux Equipiers Bridge';
|
||||
const URI = 'https://www.bourse-aux-equipiers.com';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filtrer par mots clés',
|
||||
'title' => 'Entrez le mot clé à filtrer ici'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type de recherche',
|
||||
'title' => 'Afficher seuleument un certain type d\'annonce',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Toutes les annonces' => false,
|
||||
'Les embarquements' => 'boat',
|
||||
'Les skippers' => 'skipper',
|
||||
'Les équipiers' => 'crew'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('main article');
|
||||
foreach ($annonces as $annonce) {
|
||||
$detail = $annonce->find('footer a', 0);
|
||||
|
||||
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
|
||||
if (!$htmlDetail)
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $annonce->find('header h2', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $detail->href;
|
||||
|
||||
$content = $htmlDetail->find('article p', 0)->innertext;
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = $this->remove_accents(strtolower($this->getInput('keyword')));
|
||||
$cleanTitle = $this->remove_accents(strtolower($item['title']));
|
||||
if (strpos($cleanTitle, $keyword) === false) {
|
||||
$cleanContent = $this->remove_accents(strtolower($content));
|
||||
if (strpos($cleanContent, $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
$image = $htmlDetail->find('#zoom', 0);
|
||||
if ($image) {
|
||||
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
if (!empty($this->getInput('type'))) {
|
||||
if ($this->getInput('type') == 'boat') {
|
||||
$uri .= '/embarquements.html';
|
||||
} elseif ($this->getInput('type') == 'skipper') {
|
||||
$uri .= '/skippers.html';
|
||||
} else {
|
||||
$uri .= '/equipiers.html';
|
||||
}
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function remove_accents($string) {
|
||||
$chars = array(
|
||||
// Decompositions for Latin-1 Supplement
|
||||
'ª' => 'a', 'º' => 'o',
|
||||
'À' => 'A', 'Á' => 'A',
|
||||
'Â' => 'A', 'Ã' => 'A',
|
||||
'Ä' => 'A', 'Å' => 'A',
|
||||
'Æ' => 'AE', 'Ç' => 'C',
|
||||
'È' => 'E', 'É' => 'E',
|
||||
'Ê' => 'E', 'Ë' => 'E',
|
||||
'Ì' => 'I', 'Í' => 'I',
|
||||
'Î' => 'I', 'Ï' => 'I',
|
||||
'Ð' => 'D', 'Ñ' => 'N',
|
||||
'Ò' => 'O', 'Ó' => 'O',
|
||||
'Ô' => 'O', 'Õ' => 'O',
|
||||
'Ö' => 'O', 'Ù' => 'U',
|
||||
'Ú' => 'U', 'Û' => 'U',
|
||||
'Ü' => 'U', 'Ý' => 'Y',
|
||||
'Þ' => 'TH', 'ß' => 's',
|
||||
'à' => 'a', 'á' => 'a',
|
||||
'â' => 'a', 'ã' => 'a',
|
||||
'ä' => 'a', 'å' => 'a',
|
||||
'æ' => 'ae', 'ç' => 'c',
|
||||
'è' => 'e', 'é' => 'e',
|
||||
'ê' => 'e', 'ë' => 'e',
|
||||
'ì' => 'i', 'í' => 'i',
|
||||
'î' => 'i', 'ï' => 'i',
|
||||
'ð' => 'd', 'ñ' => 'n',
|
||||
'ò' => 'o', 'ó' => 'o',
|
||||
'ô' => 'o', 'õ' => 'o',
|
||||
'ö' => 'o', 'ø' => 'o',
|
||||
'ù' => 'u', 'ú' => 'u',
|
||||
'û' => 'u', 'ü' => 'u',
|
||||
'ý' => 'y', 'þ' => 'th',
|
||||
'ÿ' => 'y', 'Ø' => 'O',
|
||||
// Decompositions for Latin Extended-A
|
||||
'Ā' => 'A', 'ā' => 'a',
|
||||
'Ă' => 'A', 'ă' => 'a',
|
||||
'Ą' => 'A', 'ą' => 'a',
|
||||
'Ć' => 'C', 'ć' => 'c',
|
||||
'Ĉ' => 'C', 'ĉ' => 'c',
|
||||
'Ċ' => 'C', 'ċ' => 'c',
|
||||
'Č' => 'C', 'č' => 'c',
|
||||
'Ď' => 'D', 'ď' => 'd',
|
||||
'Đ' => 'D', 'đ' => 'd',
|
||||
'Ē' => 'E', 'ē' => 'e',
|
||||
'Ĕ' => 'E', 'ĕ' => 'e',
|
||||
'Ė' => 'E', 'ė' => 'e',
|
||||
'Ę' => 'E', 'ę' => 'e',
|
||||
'Ě' => 'E', 'ě' => 'e',
|
||||
'Ĝ' => 'G', 'ĝ' => 'g',
|
||||
'Ğ' => 'G', 'ğ' => 'g',
|
||||
'Ġ' => 'G', 'ġ' => 'g',
|
||||
'Ģ' => 'G', 'ģ' => 'g',
|
||||
'Ĥ' => 'H', 'ĥ' => 'h',
|
||||
'Ħ' => 'H', 'ħ' => 'h',
|
||||
'Ĩ' => 'I', 'ĩ' => 'i',
|
||||
'Ī' => 'I', 'ī' => 'i',
|
||||
'Ĭ' => 'I', 'ĭ' => 'i',
|
||||
'Į' => 'I', 'į' => 'i',
|
||||
'İ' => 'I', 'ı' => 'i',
|
||||
'IJ' => 'IJ', 'ij' => 'ij',
|
||||
'Ĵ' => 'J', 'ĵ' => 'j',
|
||||
'Ķ' => 'K', 'ķ' => 'k',
|
||||
'ĸ' => 'k', 'Ĺ' => 'L',
|
||||
'ĺ' => 'l', 'Ļ' => 'L',
|
||||
'ļ' => 'l', 'Ľ' => 'L',
|
||||
'ľ' => 'l', 'Ŀ' => 'L',
|
||||
'ŀ' => 'l', 'Ł' => 'L',
|
||||
'ł' => 'l', 'Ń' => 'N',
|
||||
'ń' => 'n', 'Ņ' => 'N',
|
||||
'ņ' => 'n', 'Ň' => 'N',
|
||||
'ň' => 'n', 'ʼn' => 'n',
|
||||
'Ŋ' => 'N', 'ŋ' => 'n',
|
||||
'Ō' => 'O', 'ō' => 'o',
|
||||
'Ŏ' => 'O', 'ŏ' => 'o',
|
||||
'Ő' => 'O', 'ő' => 'o',
|
||||
'Œ' => 'OE', 'œ' => 'oe',
|
||||
'Ŕ' => 'R', 'ŕ' => 'r',
|
||||
'Ŗ' => 'R', 'ŗ' => 'r',
|
||||
'Ř' => 'R', 'ř' => 'r',
|
||||
'Ś' => 'S', 'ś' => 's',
|
||||
'Ŝ' => 'S', 'ŝ' => 's',
|
||||
'Ş' => 'S', 'ş' => 's',
|
||||
'Š' => 'S', 'š' => 's',
|
||||
'Ţ' => 'T', 'ţ' => 't',
|
||||
'Ť' => 'T', 'ť' => 't',
|
||||
'Ŧ' => 'T', 'ŧ' => 't',
|
||||
'Ũ' => 'U', 'ũ' => 'u',
|
||||
'Ū' => 'U', 'ū' => 'u',
|
||||
'Ŭ' => 'U', 'ŭ' => 'u',
|
||||
'Ů' => 'U', 'ů' => 'u',
|
||||
'Ű' => 'U', 'ű' => 'u',
|
||||
'Ų' => 'U', 'ų' => 'u',
|
||||
'Ŵ' => 'W', 'ŵ' => 'w',
|
||||
'Ŷ' => 'Y', 'ŷ' => 'y',
|
||||
'Ÿ' => 'Y', 'Ź' => 'Z',
|
||||
'ź' => 'z', 'Ż' => 'Z',
|
||||
'ż' => 'z', 'Ž' => 'Z',
|
||||
'ž' => 'z', 'ſ' => 's',
|
||||
// Decompositions for Latin Extended-B
|
||||
'Ș' => 'S', 'ș' => 's',
|
||||
'Ț' => 'T', 'ț' => 't',
|
||||
// Euro Sign
|
||||
'€' => 'E',
|
||||
// GBP (Pound) Sign
|
||||
'£' => '',
|
||||
// Vowels with diacritic (Vietnamese)
|
||||
// unmarked
|
||||
'Ơ' => 'O', 'ơ' => 'o',
|
||||
'Ư' => 'U', 'ư' => 'u',
|
||||
// grave accent
|
||||
'Ầ' => 'A', 'ầ' => 'a',
|
||||
'Ằ' => 'A', 'ằ' => 'a',
|
||||
'Ề' => 'E', 'ề' => 'e',
|
||||
'Ồ' => 'O', 'ồ' => 'o',
|
||||
'Ờ' => 'O', 'ờ' => 'o',
|
||||
'Ừ' => 'U', 'ừ' => 'u',
|
||||
'Ỳ' => 'Y', 'ỳ' => 'y',
|
||||
// hook
|
||||
'Ả' => 'A', 'ả' => 'a',
|
||||
'Ẩ' => 'A', 'ẩ' => 'a',
|
||||
'Ẳ' => 'A', 'ẳ' => 'a',
|
||||
'Ẻ' => 'E', 'ẻ' => 'e',
|
||||
'Ể' => 'E', 'ể' => 'e',
|
||||
'Ỉ' => 'I', 'ỉ' => 'i',
|
||||
'Ỏ' => 'O', 'ỏ' => 'o',
|
||||
'Ổ' => 'O', 'ổ' => 'o',
|
||||
'Ở' => 'O', 'ở' => 'o',
|
||||
'Ủ' => 'U', 'ủ' => 'u',
|
||||
'Ử' => 'U', 'ử' => 'u',
|
||||
'Ỷ' => 'Y', 'ỷ' => 'y',
|
||||
// tilde
|
||||
'Ẫ' => 'A', 'ẫ' => 'a',
|
||||
'Ẵ' => 'A', 'ẵ' => 'a',
|
||||
'Ẽ' => 'E', 'ẽ' => 'e',
|
||||
'Ễ' => 'E', 'ễ' => 'e',
|
||||
'Ỗ' => 'O', 'ỗ' => 'o',
|
||||
'Ỡ' => 'O', 'ỡ' => 'o',
|
||||
'Ữ' => 'U', 'ữ' => 'u',
|
||||
'Ỹ' => 'Y', 'ỹ' => 'y',
|
||||
// acute accent
|
||||
'Ấ' => 'A', 'ấ' => 'a',
|
||||
'Ắ' => 'A', 'ắ' => 'a',
|
||||
'Ế' => 'E', 'ế' => 'e',
|
||||
'Ố' => 'O', 'ố' => 'o',
|
||||
'Ớ' => 'O', 'ớ' => 'o',
|
||||
'Ứ' => 'U', 'ứ' => 'u',
|
||||
// dot below
|
||||
'Ạ' => 'A', 'ạ' => 'a',
|
||||
'Ậ' => 'A', 'ậ' => 'a',
|
||||
'Ặ' => 'A', 'ặ' => 'a',
|
||||
'Ẹ' => 'E', 'ẹ' => 'e',
|
||||
'Ệ' => 'E', 'ệ' => 'e',
|
||||
'Ị' => 'I', 'ị' => 'i',
|
||||
'Ọ' => 'O', 'ọ' => 'o',
|
||||
'Ộ' => 'O', 'ộ' => 'o',
|
||||
'Ợ' => 'O', 'ợ' => 'o',
|
||||
'Ụ' => 'U', 'ụ' => 'u',
|
||||
'Ự' => 'U', 'ự' => 'u',
|
||||
'Ỵ' => 'Y', 'ỵ' => 'y',
|
||||
// Vowels with diacritic (Chinese, Hanyu Pinyin)
|
||||
'ɑ' => 'a',
|
||||
// macron
|
||||
'Ǖ' => 'U', 'ǖ' => 'u',
|
||||
// acute accent
|
||||
'Ǘ' => 'U', 'ǘ' => 'u',
|
||||
// caron
|
||||
'Ǎ' => 'A', 'ǎ' => 'a',
|
||||
'Ǐ' => 'I', 'ǐ' => 'i',
|
||||
'Ǒ' => 'O', 'ǒ' => 'o',
|
||||
'Ǔ' => 'U', 'ǔ' => 'u',
|
||||
'Ǚ' => 'U', 'ǚ' => 'u',
|
||||
// grave accent
|
||||
'Ǜ' => 'U', 'ǜ' => 'u',
|
||||
);
|
||||
|
||||
$string = strtr($string, $chars);
|
||||
|
||||
return $string;
|
||||
}
|
||||
}
|
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;
|
||||
}
|
||||
}
|
103
bridges/BakaUpdatesMangaReleasesBridge.php
Normal file
103
bridges/BakaUpdatesMangaReleasesBridge.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
|
||||
const NAME = 'Baka Updates Manga Releases';
|
||||
const URI = 'https://www.mangaupdates.com/';
|
||||
const DESCRIPTION = 'Get the latest series releases';
|
||||
const MAINTAINER = 'fulmeek';
|
||||
const PARAMETERS = array(array(
|
||||
'series_id' => array(
|
||||
'name' => 'Series ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '12345'
|
||||
)
|
||||
));
|
||||
const LIMIT_COLS = 5;
|
||||
const LIMIT_ITEMS = 10;
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Series not found');
|
||||
|
||||
// content is an unstructured pile of divs, ugly to parse
|
||||
$cols = $html->find('div#main_content div.row > div.text');
|
||||
if (!$cols)
|
||||
returnServerError('No releases');
|
||||
|
||||
$rows = array_slice(
|
||||
array_chunk($cols, self::LIMIT_COLS), 0, self::LIMIT_ITEMS
|
||||
);
|
||||
|
||||
if (isset($rows[0][1])) {
|
||||
$this->feedName = $this->filterHTML($rows[0][1]->plaintext);
|
||||
}
|
||||
|
||||
foreach($rows as $cols) {
|
||||
if (count($cols) < self::LIMIT_COLS) continue;
|
||||
|
||||
$item = array();
|
||||
$title = array();
|
||||
|
||||
$item['content'] = '';
|
||||
|
||||
$objDate = $cols[0];
|
||||
if ($objDate)
|
||||
$item['timestamp'] = strtotime($objDate->plaintext);
|
||||
|
||||
$objTitle = $cols[1];
|
||||
if ($objTitle) {
|
||||
$title[] = $this->filterHTML($objTitle->plaintext);
|
||||
$item['content'] .= '<p>Series: ' . $this->filterText($objTitle->innertext) . '</p>';
|
||||
}
|
||||
|
||||
$objVolume = $cols[2];
|
||||
if ($objVolume && !empty($objVolume->plaintext))
|
||||
$title[] = 'Vol.' . $objVolume->plaintext;
|
||||
|
||||
$objChapter = $cols[3];
|
||||
if ($objChapter && !empty($objChapter->plaintext))
|
||||
$title[] = 'Chp.' . $objChapter->plaintext;
|
||||
|
||||
$objAuthor = $cols[4];
|
||||
if ($objAuthor && !empty($objAuthor->plaintext)) {
|
||||
$item['author'] = $this->filterHTML($objAuthor->plaintext);
|
||||
$item['content'] .= '<p>Groups: ' . $this->filterText($objAuthor->innertext) . '</p>';
|
||||
}
|
||||
|
||||
$item['title'] = implode(' ', $title);
|
||||
$item['uri'] = $this->getURI();
|
||||
$item['uid'] = $this->getSanitizedHash($item['title']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
$series_id = $this->getInput('series_id');
|
||||
if (!empty($series_id)) {
|
||||
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
|
||||
}
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!empty($this->feedName)) {
|
||||
return $this->feedName . ' - ' . self::NAME;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getSanitizedHash($string) {
|
||||
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
|
||||
}
|
||||
|
||||
private function filterText($text) {
|
||||
return rtrim($text, '* ');
|
||||
}
|
||||
|
||||
private function filterHTML($text) {
|
||||
return $this->filterText(html_entity_decode($text));
|
||||
}
|
||||
}
|
@@ -13,44 +13,72 @@ class BandcampBridge extends BridgeAbstract {
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
const IMGURI = 'https://f4.bcbits.com/';
|
||||
const IMGSIZE_300PX = 23;
|
||||
const IMGSIZE_700PX = 16;
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s4.bcbits.com/img/bc_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for this query.');
|
||||
$url = self::URI . 'api/hub/1/dig_deeper';
|
||||
$data = $this->buildRequestJson();
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data)
|
||||
);
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
);
|
||||
$content = getContents($url, $header, $opts)
|
||||
or returnServerError('Could not complete request to: ' . $url);
|
||||
|
||||
foreach($html->find('li.item') as $release) {
|
||||
$script = $release->find('div.art', 0)->getAttribute('onclick');
|
||||
$uri = ltrim($script, "return 'url(");
|
||||
$uri = rtrim($uri, "')");
|
||||
$json = json_decode($content);
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $release->find('div.itemsubtext', 0)->plaintext
|
||||
. ' - '
|
||||
. $release->find('div.itemtext', 0)->plaintext;
|
||||
if ($json->ok !== true) {
|
||||
returnServerError('Invalid response');
|
||||
}
|
||||
|
||||
$item['title'] = $release->find('div.itemsubtext', 0)->plaintext
|
||||
. ' - '
|
||||
. $release->find('div.itemtext', 0)->plaintext;
|
||||
foreach ($json->items as $entry) {
|
||||
$url = $entry->tralbum_url;
|
||||
$artist = $entry->artist;
|
||||
$title = $entry->title;
|
||||
// e.g. record label is the releaser, but not the artist
|
||||
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
|
||||
|
||||
$item['content'] = '<img src="'
|
||||
. $uri
|
||||
. '"/><br/>'
|
||||
. $release->find('div.itemsubtext', 0)->plaintext
|
||||
. ' - '
|
||||
. $release->find('div.itemtext', 0)->plaintext;
|
||||
$full_title = $artist . ' - ' . $title;
|
||||
$full_artist = $artist;
|
||||
if (isset($releaser)) {
|
||||
$full_title .= ' (' . $releaser . ')';
|
||||
$full_artist .= ' (' . $releaser . ')';
|
||||
}
|
||||
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
|
||||
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
|
||||
|
||||
$item['id'] = $release->find('a', 0)->getAttribute('href');
|
||||
$item['uri'] = $release->find('a', 0)->getAttribute('href');
|
||||
$item = array(
|
||||
'uri' => $url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title
|
||||
);
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title";
|
||||
$item['enclosures'] = array($img);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date';
|
||||
}
|
||||
private function buildRequestJson(){
|
||||
$requestJson = array(
|
||||
'tag' => $this->getInput('tag'),
|
||||
'page' => 1,
|
||||
'sort' => 'date'
|
||||
);
|
||||
return json_encode($requestJson);
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
private function getImageUrl($id, $size){
|
||||
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
|
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) . '...';
|
||||
}
|
||||
}
|
@@ -1,31 +1,46 @@
|
||||
<?php
|
||||
class BlaguesDeMerdeBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'superbaillot.net';
|
||||
const MAINTAINER = 'superbaillot.net, logmanoriginal';
|
||||
const NAME = 'Blagues De Merde';
|
||||
const URI = 'http://www.blaguesdemerde.fr/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Blagues De Merde';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request BDM.');
|
||||
|
||||
foreach($html->find('article.joke_contener') as $element) {
|
||||
$item = array();
|
||||
$temp = $element->find('a');
|
||||
foreach($html->find('div.blague') as $element) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = static::URI . '#' . $element->id;
|
||||
$item['author'] = $element->find('div[class="blague-footer"] p strong', 0)->plaintext;
|
||||
|
||||
// Let the title be everything up to the first <br>
|
||||
$item['title'] = trim(explode("\n", $element->find('div.text', 0)->plaintext)[0]);
|
||||
|
||||
$item['content'] = strip_tags($element->find('div.text', 0));
|
||||
|
||||
// timestamp is part of:
|
||||
// <p>Par <strong>{author}</strong> le {date} dans <strong>{category}</strong></p>
|
||||
preg_match(
|
||||
'/.+le(.+)dans.*/',
|
||||
$element->find('div[class="blague-footer"]', 0)->plaintext,
|
||||
$matches
|
||||
);
|
||||
|
||||
$item['timestamp'] = strtotime($matches[1]);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if(isset($temp[2])) {
|
||||
$item['content'] = trim($element->find('div.joke_text_contener', 0)->innertext);
|
||||
$uri = $temp[2]->href;
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = substr($uri, (strrpos($uri, "/") + 1));
|
||||
$date = $element->find('li.bdm_date', 0)->innertext;
|
||||
$time = mktime(0, 0, 0, substr($date, 3, 2), substr($date, 0, 2), substr($date, 6, 4));
|
||||
$item['timestamp'] = $time;
|
||||
$item['author'] = $element->find('li.bdm_pseudo', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -31,6 +31,10 @@ class BloombergBridge extends BridgeAbstract
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.bwbx.io/s3/javelin/public/hub/images/favicon-black-63fe5249d3.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
|
142
bridges/BrutBridge.php
Normal file
142
bridges/BrutBridge.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?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();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
85
bridges/BundesbankBridge.php
Normal file
85
bridges/BundesbankBridge.php
Normal file
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
class BundesbankBridge extends BridgeAbstract {
|
||||
|
||||
const PARAM_LANG = 'lang';
|
||||
|
||||
const LANG_EN = 'en';
|
||||
const LANG_DE = 'de';
|
||||
|
||||
const NAME = 'Bundesbank Bridge';
|
||||
const URI = 'https://www.bundesbank.de/';
|
||||
const DESCRIPTION = 'Returns the latest studies of the Bundesbank (Germany)';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 86400; // 24 hours
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
self::PARAM_LANG => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'defaultValue' => self::LANG_DE,
|
||||
'values' => array(
|
||||
'English' => self::LANG_EN,
|
||||
'Deutsch' => self::LANG_DE
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'resource/crblob/1890/a7f48ee0ae35348748121770ba3ca009/mL/favicon-ico-data.ico';
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->getInput(self::PARAM_LANG)) {
|
||||
case self::LANG_EN: return self::URI . 'en/publications/reports/studies';
|
||||
case self::LANG_DE: return self::URI . 'de/publikationen/berichte/studien';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No response for ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
foreach($html->find('ul.resultlist li') as $study) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $study->find('.teasable__link', 0)->href;
|
||||
|
||||
// Get title without child elements (i.e. subtitle)
|
||||
$title = $study->find('.teasable__title div.h2', 0);
|
||||
|
||||
foreach($title->children as &$child) {
|
||||
$child->outertext = '';
|
||||
}
|
||||
|
||||
$item['title'] = $title->innertext;
|
||||
|
||||
// Add subtitle to the content if it exists
|
||||
$item['content'] = '';
|
||||
|
||||
if($subtitle = $study->find('.teasable__subtitle', 0)) {
|
||||
$item['content'] .= '<strong>' . $study->find('.teasable__subtitle', 0)->plaintext . '</strong>';
|
||||
}
|
||||
|
||||
$item['content'] .= '<p>' . $study->find('.teasable__text', 0)->plaintext . '</p>';
|
||||
|
||||
$item['timestamp'] = strtotime($study->find('.teasable__date', 0)->plaintext);
|
||||
|
||||
// Downloads and older studies don't have images
|
||||
if($study->find('.teasable__image', 0)) {
|
||||
$item['enclosures'] = array(
|
||||
$study->find('.teasable__image img', 0)->src
|
||||
);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
class CADBridge extends FeedExpander {
|
||||
const MAINTAINER = 'nyutag';
|
||||
const NAME = 'CAD Bridge';
|
||||
const URI = 'http://www.cad-comic.com/';
|
||||
const CACHE_TIMEOUT = 7200; //2h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://cdn2.cad-comic.com/rss.xml', 10);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
$item['content'] = $this->extractCADContent($item['uri']);
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractCADContent($url) {
|
||||
$html3 = getSimpleHTMLDOMCached($url);
|
||||
|
||||
// The request might fail due to missing https support or wrong URL
|
||||
if($html3 == false)
|
||||
return 'Daily comic not released yet';
|
||||
|
||||
$htmlpart = explode("/", $url);
|
||||
|
||||
switch ($htmlpart[3]) {
|
||||
case 'cad':
|
||||
preg_match_all("/http:\/\/cdn2\.cad-comic\.com\/comics\/cad-\S*png/", $html3, $url2);
|
||||
break;
|
||||
case 'sillies':
|
||||
preg_match_all("/http:\/\/cdn2\.cad-comic\.com\/comics\/sillies-\S*gif/", $html3, $url2);
|
||||
break;
|
||||
default:
|
||||
return 'Daily comic not released yet';
|
||||
}
|
||||
$img = implode($url2[0]);
|
||||
$html3->clear();
|
||||
unset($html3);
|
||||
if ($img == '')
|
||||
return 'Daily comic not released yet';
|
||||
return '<img src="' . $img . '"/>';
|
||||
}
|
||||
}
|
@@ -3,91 +3,107 @@ class CNETBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'CNET News';
|
||||
const URI = 'http://www.cnet.com/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns the newest articles. <br /> You may specify a
|
||||
topic found in some section URLs, else all topics are selected.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic name'
|
||||
const URI = 'https://www.cnet.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All articles' => '',
|
||||
'Apple' => 'apple',
|
||||
'Google' => 'google',
|
||||
'Microsoft' => 'tags-microsoft',
|
||||
'Computers' => 'topics-computers',
|
||||
'Mobile' => 'topics-mobile',
|
||||
'Sci-Tech' => 'topics-sci-tech',
|
||||
'Security' => 'topics-security',
|
||||
'Internet' => 'topics-internet',
|
||||
'Tech Industry' => 'topics-tech-industry'
|
||||
)
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
private function cleanArticle($article_html) {
|
||||
$offset_p = strpos($article_html, '<p>');
|
||||
$offset_figure = strpos($article_html, '<figure');
|
||||
$offset = ($offset_figure < $offset_p ? $offset_figure : $offset_p);
|
||||
$article_html = substr($article_html, $offset);
|
||||
$article_html = str_replace('href="/', 'href="' . self::URI, $article_html);
|
||||
$article_html = str_replace(' height="0"', '', $article_html);
|
||||
$article_html = str_replace('<noscript>', '', $article_html);
|
||||
$article_html = str_replace('</noscript>', '', $article_html);
|
||||
$article_html = StripWithDelimiters($article_html, '<a class="clickToEnlarge', '</a>');
|
||||
$article_html = stripWithDelimiters($article_html, '<span class="nowPlaying', '</span>');
|
||||
$article_html = stripWithDelimiters($article_html, '<span class="duration', '</span>');
|
||||
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
|
||||
$article_html = stripWithDelimiters($article_html, '<svg', '</svg>');
|
||||
return $article_html;
|
||||
}
|
||||
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
public function collectData() {
|
||||
|
||||
// Retrieve and check user input
|
||||
$topic = str_replace('-', '/', $this->getInput('topic'));
|
||||
if (!empty($topic) && (substr_count($topic, '/') > 1 || !ctype_alpha(str_replace('/', '', $topic))))
|
||||
returnClientError('Invalid topic: ' . $topic);
|
||||
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
|
||||
$html = getSimpleHTMLDOM($pageUrl)
|
||||
or returnServerError('Could not request CNET: ' . $pageUrl);
|
||||
|
||||
// Process articles
|
||||
foreach($html->find('div.assetBody, div.riverPost') as $element) {
|
||||
|
||||
if(count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
$article_title = trim($element->find('h2, h3', 0)->plaintext);
|
||||
$article_uri = self::URI . substr($element->find('a', 0)->href, 1);
|
||||
$article_thumbnail = $element->parent()->find('img[src]', 0)->src;
|
||||
$article_timestamp = strtotime($element->find('time.assetTime, div.timeAgo', 0)->plaintext);
|
||||
$article_author = trim($element->find('a[rel=author], a.name', 0)->plaintext);
|
||||
$article_content = '<p><b>' . trim($element->find('p.dek', 0)->plaintext) . '</b></p>';
|
||||
|
||||
function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
if (is_null($article_thumbnail))
|
||||
$article_thumbnail = extractFromDelimiters($element->innertext, '<img src="', '"');
|
||||
|
||||
return $string;
|
||||
}
|
||||
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, self::URI . 'news/') !== false) {
|
||||
|
||||
function cleanArticle($article_html){
|
||||
$article_html = '<p>' . substr($article_html, strpos($article_html, '<p>') + 3);
|
||||
$article_html = stripWithDelimiters($article_html, '<span class="credit">', '</span>');
|
||||
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
|
||||
$article_html = stripWithDelimiters($article_html, '<div class="shortcode related-links', '</div>');
|
||||
$article_html = stripWithDelimiters($article_html, '<a class="clickToEnlarge">', '</a>');
|
||||
return $article_html;
|
||||
}
|
||||
$article_html = getSimpleHTMLDOMCached($article_uri) or $article_html = null;
|
||||
|
||||
$pageUrl = self::URI . (empty($this->getInput('topic')) ? '' : 'topics/' . $this->getInput('topic') . '/');
|
||||
$html = getSimpleHTMLDOM($pageUrl) or returnServerError('Could not request CNET: ' . $pageUrl);
|
||||
$limit = 0;
|
||||
if (!is_null($article_html)) {
|
||||
|
||||
foreach($html->find('div.assetBody') as $element) {
|
||||
if($limit < 8) {
|
||||
$article_title = trim($element->find('h2', 0)->plaintext);
|
||||
$article_uri = self::URI . ($element->find('a', 0)->href);
|
||||
$article_timestamp = strtotime($element->find('time.assetTime', 0)->plaintext);
|
||||
$article_author = trim($element->find('a[rel=author]', 0)->plaintext);
|
||||
if (empty($article_thumbnail))
|
||||
$article_thumbnail = $article_html->find('div.originalImage', 0);
|
||||
if (empty($article_thumbnail))
|
||||
$article_thumbnail = $article_html->find('span.imageContainer', 0);
|
||||
if (is_object($article_thumbnail))
|
||||
$article_thumbnail = $article_thumbnail->find('img', 0)->src;
|
||||
|
||||
if(!empty($article_title) && !empty($article_uri) && strpos($article_uri, '/news/') !== false) {
|
||||
$article_html = getSimpleHTMLDOM($article_uri)
|
||||
or returnServerError('Could not request CNET: ' . $article_uri);
|
||||
$article_content = trim(
|
||||
cleanArticle(
|
||||
$article_content .= trim(
|
||||
$this->cleanArticle(
|
||||
extractFromDelimiters(
|
||||
$article_html,
|
||||
'<div class="articleContent',
|
||||
'<footer>'
|
||||
$article_html, '<article', '<footer'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
$item['author'] = $article_author;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$item['content'] = $article_content;
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
$item['author'] = $article_author;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$item['enclosures'] = array($article_thumbnail);
|
||||
$item['content'] = $article_content;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('topic'))) {
|
||||
$topic = $this->getInput('topic');
|
||||
return 'CNET News Bridge' . (empty($topic) ? '' : ' - ' . $topic);
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
134
bridges/CachetBridge.php
Normal file
134
bridges/CachetBridge.php
Normal file
@@ -0,0 +1,134 @@
|
||||
<?php
|
||||
|
||||
class CachetBridge extends BridgeAbstract {
|
||||
const NAME = 'Cachet Bridge';
|
||||
const URI = 'https://cachethq.io/';
|
||||
const DESCRIPTION = 'Returns status updates from any Cachet installation';
|
||||
const MAINTAINER = 'klimplant';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'host' => array(
|
||||
'name' => 'Cachet installation',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'The URL of the Cachet installation',
|
||||
'exampleValue' => 'https://demo.cachethq.io/',
|
||||
), 'additional_info' => array(
|
||||
'name' => 'Additional Timestamps',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Whether to include the given timestamps'
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 300;
|
||||
|
||||
private $componentCache = [];
|
||||
|
||||
public function getURI() {
|
||||
return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host');
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the ping request to the cache API
|
||||
*
|
||||
* @param string $ping
|
||||
* @return boolean
|
||||
*/
|
||||
private function validatePing($ping) {
|
||||
$ping = json_decode($ping);
|
||||
if ($ping === null) {
|
||||
return false;
|
||||
}
|
||||
return $ping->data === 'Pong!';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the component name of a cachat component
|
||||
*
|
||||
* @param integer $id
|
||||
* @return string
|
||||
*/
|
||||
private function getComponentName($id) {
|
||||
if ($id === 0) {
|
||||
return '';
|
||||
}
|
||||
if (array_key_exists($id, $this->componentCache)) {
|
||||
return $this->componentCache[$id];
|
||||
}
|
||||
|
||||
$component = getContents($this->getURI() . '/api/v1/components/' . $id);
|
||||
$component = json_decode($component);
|
||||
if ($component === null) {
|
||||
return '';
|
||||
}
|
||||
return $component->data->name;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$ping = getContents(urljoin($this->getURI(), '/api/v1/ping'));
|
||||
if (!$this->validatePing($ping)) {
|
||||
returnClientError('Provided URI is invalid!');
|
||||
}
|
||||
|
||||
$url = urljoin($this->getURI(), '/api/v1/incidents?sort=id&order=desc');
|
||||
$incidents = getContents($url);
|
||||
$incidents = json_decode($incidents);
|
||||
if ($incidents === null) {
|
||||
returnClientError('/api/v1/incidents returned no valid json');
|
||||
}
|
||||
|
||||
usort($incidents->data, function ($a, $b) {
|
||||
$timeA = strtotime($a->updated_at);
|
||||
$timeB = strtotime($b->updated_at);
|
||||
return $timeA > $timeB ? -1 : 1;
|
||||
});
|
||||
|
||||
foreach ($incidents->data as $incident) {
|
||||
|
||||
if (isset($incident->permalink)) {
|
||||
$permalink = $incident->permalink;
|
||||
} else {
|
||||
$permalink = urljoin($this->getURI(), '/incident/' . $incident->id);
|
||||
}
|
||||
|
||||
$title = $incident->human_status . ': ' . $incident->name;
|
||||
$message = '';
|
||||
if ($this->getInput('additional_info')) {
|
||||
if (isset($incident->occurred_at)) {
|
||||
$message .= 'Occurred at: ' . $incident->occurred_at . "\r\n";
|
||||
}
|
||||
if (isset($incident->scheduled_at)) {
|
||||
$message .= 'Scheduled at: ' . $incident->scheduled_at . "\r\n";
|
||||
}
|
||||
if (isset($incident->created_at)) {
|
||||
$message .= 'Created at: ' . $incident->created_at . "\r\n";
|
||||
}
|
||||
if (isset($incident->updated_at)) {
|
||||
$message .= 'Updated at: ' . $incident->updated_at . "\r\n\r\n";
|
||||
}
|
||||
}
|
||||
|
||||
$message .= $incident->message;
|
||||
$content = nl2br($message);
|
||||
$componentName = $this->getComponentName($incident->component_id);
|
||||
$uidOrig = $permalink . $incident->created_at;
|
||||
$uid = hash('sha512', $uidOrig);
|
||||
$timestamp = strtotime($incident->created_at);
|
||||
$categories = [];
|
||||
$categories[] = $incident->human_status;
|
||||
if ($componentName !== '') {
|
||||
$categories[] = $componentName;
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = $permalink;
|
||||
$item['title'] = $title;
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content;
|
||||
$item['uid'] = $uid;
|
||||
$item['categories'] = $categories;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
28
bridges/ChristianDailyReporterBridge.php
Normal file
28
bridges/ChristianDailyReporterBridge.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
class ChristianDailyReporterBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'rogerdc';
|
||||
const NAME = 'Christian Daily Reporter Unofficial RSS';
|
||||
const URI = 'https://www.christiandailyreporter.com/';
|
||||
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
|
||||
// const CACHE_TIMEOUT = 86400; // 1 day
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'images/cdrfavicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.christiandailyreporter.com/';
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Christian Daily Reporter.');
|
||||
foreach($html->find('div.top p a,div.column p a') as $element) {
|
||||
$item = array();
|
||||
// Title
|
||||
$item['title'] = $element->innertext;
|
||||
// URL
|
||||
$item['uri'] = $element->href;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
22
bridges/ComboiosDePortugalBridge.php
Normal file
22
bridges/ComboiosDePortugalBridge.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
class ComboiosDePortugalBridge extends BridgeAbstract {
|
||||
const NAME = 'CP | Avisos';
|
||||
const BASE_URI = 'https://www.cp.pt';
|
||||
const URI = self::BASE_URI . '/passageiros/pt';
|
||||
const DESCRIPTION = 'Comboios de Portugal | Avisos';
|
||||
const MAINTAINER = 'somini';
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos')
|
||||
or returnServerError('Could not load content');
|
||||
|
||||
foreach($html->find('.warnings-table a') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $element->innertext;
|
||||
$item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href)));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ class CommonDreamsBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'nyutag';
|
||||
const NAME = 'CommonDreams Bridge';
|
||||
const URI = 'http://www.commondreams.org/';
|
||||
const URI = 'https://www.commondreams.org/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData(){
|
||||
|
96
bridges/ContainerLinuxReleasesBridge.php
Normal file
96
bridges/ContainerLinuxReleasesBridge.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const NAME = 'Core OS Container Linux Releases Bridge';
|
||||
const URI = 'https://coreos.com/releases/';
|
||||
const DESCRIPTION = 'Returns the releases notes for Container Linux';
|
||||
|
||||
const STABLE = 'stable';
|
||||
const BETA = 'beta';
|
||||
const ALPHA = 'alpha';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'channel' => [
|
||||
'name' => 'Release Channel',
|
||||
'type' => 'list',
|
||||
'defaultValue' => self::STABLE,
|
||||
'values' => [
|
||||
'Stable' => self::STABLE,
|
||||
'Beta' => self::BETA,
|
||||
'Alpha' => self::ALPHA,
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
private function getReleaseFeed($jsonUrl) {
|
||||
$json = getContents($jsonUrl)
|
||||
or returnServerError('Could not request Core OS Website.');
|
||||
return json_decode($json, true);
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://coreos.com/assets/ico/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$data = $this->getReleaseFeed($this->getJsonUri());
|
||||
|
||||
foreach ($data as $releaseVersion => $release) {
|
||||
$item = [];
|
||||
|
||||
$item['uri'] = "https://coreos.com/releases/#$releaseVersion";
|
||||
$item['title'] = $releaseVersion;
|
||||
|
||||
$content = $release['release_notes'];
|
||||
$content .= <<<EOT
|
||||
|
||||
Major Software:
|
||||
* Kernel: {$release['major_software']['kernel'][0]}
|
||||
* Docker: {$release['major_software']['docker'][0]}
|
||||
* etcd: {$release['major_software']['etcd'][0]}
|
||||
EOT;
|
||||
$item['timestamp'] = strtotime($release['release_date']);
|
||||
|
||||
// Based on https://gist.github.com/jbroadway/2836900
|
||||
// Links
|
||||
$regex = '/\[([^\[]+)\]\(([^\)]+)\)/';
|
||||
$replacement = '<a href=\'\2\'>\1</a>';
|
||||
$item['content'] = preg_replace($regex, $replacement, $content);
|
||||
|
||||
// Headings
|
||||
$regex = '/^(.*)\:\s?$/m';
|
||||
$replacement = '<h3>\1</h3>';
|
||||
$item['content'] = preg_replace($regex, $replacement, $item['content']);
|
||||
|
||||
// Lists
|
||||
$regex = '/\n\s*[\*|\-](.*)/';
|
||||
$item['content'] = preg_replace_callback ($regex, function($regs) {
|
||||
$item = $regs[1];
|
||||
return sprintf ('<ul><li>%s</li></ul>', trim ($item));
|
||||
}, $item['content']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getJsonUri() {
|
||||
$channel = $this->getInput('channel');
|
||||
|
||||
return "https://coreos.com/releases/releases-$channel.json";
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('channel'))) {
|
||||
return 'Container Linux Releases: ' . $this->getInput('channel') . ' Channel';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -25,7 +25,7 @@ class CopieDoubleBridge extends BridgeAbstract {
|
||||
} elseif(strpos($element->innertext, '/images/suivant.gif') === false) {
|
||||
$a = $element->find('a', 0);
|
||||
$item['uri'] = self::URI . $a->href;
|
||||
$content = str_replace('src="/', 'src="/' . self::URI, $element->find("td", 0)->innertext);
|
||||
$content = str_replace('src="/', 'src="/' . self::URI, $element->find('td', 0)->innertext);
|
||||
$content = str_replace('href="/', 'href="' . self::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$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';
|
||||
|
||||
@@ -11,7 +11,7 @@ class CourrierInternationalBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error.');
|
||||
|
||||
$element = $html->find("article");
|
||||
$element = $html->find('article');
|
||||
$article_count = 1;
|
||||
|
||||
foreach($element as $article) {
|
||||
|
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
class CpasbienBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'lagaisse';
|
||||
const NAME = 'Cpasbien Bridge';
|
||||
const URI = 'http://www.cpasbien.cm';
|
||||
const CACHE_TIMEOUT = 86400; // 24h
|
||||
const DESCRIPTION = 'Returns latest torrents from a request query';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'q' => array(
|
||||
'name' => 'Search',
|
||||
'required' => true,
|
||||
'title' => 'Type your search'
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$request = str_replace(" ", "-", trim($this->getInput('q')));
|
||||
$html = getSimpleHTMLDOM(self::URI . '/recherche/' . urlencode($request) . '.html')
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
foreach($html->find('#gauche', 0)->find('div') as $episode) {
|
||||
if($episode->getAttribute('class') == 'ligne0'
|
||||
|| $episode->getAttribute('class') == 'ligne1') {
|
||||
|
||||
$urlepisode = $episode->find('a', 0)->getAttribute('href');
|
||||
$htmlepisode = getSimpleHTMLDOMCached($urlepisode, 86400 * 366 * 30);
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $episode->find('a', 0)->text();
|
||||
$item['title'] = $episode->find('a', 0)->text();
|
||||
$item['pubdate'] = $this->getCachedDate($urlepisode);
|
||||
$textefiche = $htmlepisode->find('#textefiche', 0)->find('p', 1);
|
||||
|
||||
if(isset($textefiche)) {
|
||||
$item['content'] = $textefiche->text();
|
||||
} else {
|
||||
$p = $htmlepisode->find('#textefiche', 0)->find('p');
|
||||
if(!empty($p)) {
|
||||
$item['content'] = $htmlepisode->find('#textefiche', 0)->find('p', 0)->text();
|
||||
}
|
||||
}
|
||||
|
||||
$item['id'] = $episode->find('a', 0)->getAttribute('href');
|
||||
$item['uri'] = self::URI . $htmlepisode->find('#telecharger', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return $this->getInput('q') . ' : ' . self::NAME;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getCachedDate($url){
|
||||
debugMessage('getting pubdate from url ' . $url . '');
|
||||
|
||||
// Initialize cache
|
||||
$cache = Cache::create('FileCache');
|
||||
$cache->setPath(CACHE_DIR . '/pages');
|
||||
|
||||
$params = [$url];
|
||||
$cache->setParameters($params);
|
||||
|
||||
// Get cachefile timestamp
|
||||
$time = $cache->getTime();
|
||||
return ($time !== false ? $time : time());
|
||||
}
|
||||
}
|
227
bridges/CrewbayBridge.php
Normal file
227
bridges/CrewbayBridge.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
class CrewbayBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Crewbay Bridge';
|
||||
const URI = 'https://www.crewbay.com';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filter by keyword',
|
||||
'title' => 'Enter the keyword to filter here'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type of search',
|
||||
'title' => 'Choose between finding a boat or a crew',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Find a boat' => 'boats',
|
||||
'Find a crew' => 'crew'
|
||||
)
|
||||
),
|
||||
'status' => array(
|
||||
'name' => 'Status on the boat',
|
||||
'title' => 'Choose between recreational or professional classified ads',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Recreational' => 'recreational',
|
||||
'Professional' => 'professional'
|
||||
)
|
||||
),
|
||||
'recreational_position' => array(
|
||||
'name' => 'Recreational position wanted',
|
||||
'title' => 'Filter by recreational position you wanted aboard',
|
||||
'required' => false,
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Amateur Crew' => 'Amateur Crew',
|
||||
'Friendship' => 'Friendship',
|
||||
'Competent Crew' => 'Competent Crew',
|
||||
'Racing' => 'Racing',
|
||||
'Voluntary work' => 'Voluntary work',
|
||||
'Mile building' => 'Mile building'
|
||||
)
|
||||
),
|
||||
'professional_position' => array(
|
||||
'name' => 'Professional position wanted',
|
||||
'title' => 'Filter by professional position you wanted aboard',
|
||||
'required' => false,
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'1st Engineer' => '1st Engineer',
|
||||
'1st Mate' => '1st Mate',
|
||||
'Beautician' => 'Beautician',
|
||||
'Bosun' => 'Bosun',
|
||||
'Captain' => 'Captain',
|
||||
'Chef' => 'Chef',
|
||||
'Steward(ess)' => 'Steward(ess)',
|
||||
'Deckhand' => 'Deckhand',
|
||||
'Delivery Crew' => 'Delivery Crew',
|
||||
'Dive Instructor' => 'Dive Instructor',
|
||||
'Masseur' => 'Masseur',
|
||||
'Medical Staff' => 'Medical Staff',
|
||||
'Nanny' => 'Nanny',
|
||||
'Navigator' => 'Navigator',
|
||||
'Racing Crew' => 'Racing Crew',
|
||||
'Teacher' => 'Teacher',
|
||||
'Electrical Engineer' => 'Electrical Engineer',
|
||||
'Fitter' => 'Fitter',
|
||||
'2nd Engineer' => '2nd Engineer',
|
||||
'3rd Engineer' => '3rd Engineer',
|
||||
'Lead Deckhand' => 'Lead Deckhand',
|
||||
'Security Officer' => 'Security Officer',
|
||||
'O.O.W' => 'O.O.W',
|
||||
'1st Officer' => '1st Officer',
|
||||
'2nd Officer' => '2nd Officer',
|
||||
'3rd Officer' => '3rd Officer',
|
||||
'Captain/Engineer' => 'Captain/Engineer',
|
||||
'Hairdresser' => 'Hairdresser',
|
||||
'Fitness Trainer' => 'Fitness Trainer',
|
||||
'Laundry' => 'Laundry',
|
||||
'Solo Steward/ess' => 'Solo Steward/ess',
|
||||
'Stew/Deck' => 'Stew/Deck',
|
||||
'2nd Steward/ess' => '2nd Steward/ess',
|
||||
'3rd Steward/ess' => '3rd Steward/ess',
|
||||
'Chief Steward/ess' => 'Chief Steward/ess',
|
||||
'Head Housekeeper' => 'Head Housekeeper',
|
||||
'Purser' => 'Purser',
|
||||
'Cook' => 'Cook',
|
||||
'Cook/Stew' => 'Cook/Stew',
|
||||
'2nd Chef' => '2nd Chef',
|
||||
'Head Chef' => 'Head Chef',
|
||||
'Administrator' => 'Administrator',
|
||||
'P.A' => 'P.A',
|
||||
'Villa staff' => 'Villa staff',
|
||||
'Housekeeping/Stew' => 'Housekeeping/Stew',
|
||||
'Stew/Beautician' => 'Stew/Beautician',
|
||||
'Stew/Masseuse' => 'Stew/Masseuse',
|
||||
'Manager' => 'Manager',
|
||||
'Sailing instructor' => 'Sailing instructor'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('#SearchResults div.result');
|
||||
$limit = 0;
|
||||
|
||||
foreach ($annonces as $annonce) {
|
||||
$detail = $annonce->find('.btn--profile', 0);
|
||||
$htmlDetail = getSimpleHTMLDOMCached($detail->href);
|
||||
|
||||
if (!empty($this->getInput('recreational_position')) || !empty($this->getInput('professional_position'))) {
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
if ($this->getInput('status') == 'professional') {
|
||||
$positions = array($annonce->find('.title .position', 0)->plaintext);
|
||||
} else {
|
||||
$positions = array(str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext));
|
||||
}
|
||||
} else {
|
||||
$list = $htmlDetail->find('.viewer-details .viewer-list');
|
||||
$positions = explode("\r\n", end($list)->find('span.value', 0)->plaintext);
|
||||
}
|
||||
|
||||
$found = false;
|
||||
$keyword = $this->getInput('status') == 'professional' ? 'professional_position' : 'recreational_position';
|
||||
foreach ($positions as $position) {
|
||||
if (strpos(trim($position), $this->getInput($keyword)) !== false) {
|
||||
$found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$found) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
$titleSelector = '.title h2';
|
||||
} else {
|
||||
$titleSelector = '.layout__item h2';
|
||||
}
|
||||
$userName = $annonce->find('.result--description a', 0)->plaintext;
|
||||
$annonceTitle = trim($annonce->find($titleSelector, 0)->plaintext);
|
||||
if (empty($annonceTitle)) {
|
||||
$item['title'] = $userName;
|
||||
} else {
|
||||
$item['title'] = $userName . ' - ' . $annonceTitle;
|
||||
}
|
||||
|
||||
$item['uri'] = $detail->href;
|
||||
$images = $annonce->find('.avatar img');
|
||||
$item['enclosures'] = array(end($images)->getAttribute('src'));
|
||||
|
||||
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
|
||||
|
||||
$sections = $htmlDetail->find('.viewer-container .viewer-section');
|
||||
foreach ($sections as $section) {
|
||||
if ($section->find('.viewer-section-title', 0)) {
|
||||
$class = str_replace('viewer-', '', explode(' ', $section->getAttribute('class'))[0]);
|
||||
if (!in_array($class, array('apply', 'photos', 'reviews', 'contact', 'experience', 'qa'))) {
|
||||
// Basic sections
|
||||
$content .= $section->find('.viewer-section-title h3', 0)->outertext;
|
||||
$content .= $section->find('.viewer-section-content', 0)->innertext;
|
||||
}
|
||||
} else {
|
||||
// Info section
|
||||
$content .= $section->find('.viewer-section-content h3', 0)->outertext;
|
||||
$content .= $section->find('.viewer-section-content p', 0)->outertext;
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = strtolower($this->getInput('keyword'));
|
||||
if (strpos(strtolower($item['title']), $keyword) === false) {
|
||||
if (strpos(strtolower($content), $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = $content;
|
||||
|
||||
$tags = $htmlDetail->find('li.viewer-tags--tag');
|
||||
foreach ($tags as $tag) {
|
||||
if (!isset($item['categories'])) {
|
||||
$item['categories'] = array();
|
||||
}
|
||||
$text = trim($tag->plaintext);
|
||||
if (!in_array($text, $item['categories'])) {
|
||||
$item['categories'][] = $text;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
$limit += 1;
|
||||
|
||||
if ($limit == 10) break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
$uri .= '/boats';
|
||||
} else {
|
||||
$uri .= '/crew';
|
||||
}
|
||||
|
||||
if ($this->getInput('status') == 'professional') {
|
||||
$uri .= '/professional';
|
||||
} else {
|
||||
$uri .= '/recreational';
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
@@ -48,6 +48,10 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$limit = 5;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class DanbooruBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const MAINTAINER = 'mitsukarenai, logmanoriginal';
|
||||
const NAME = 'Danbooru';
|
||||
const URI = 'http://donmai.us/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
@@ -41,7 +41,7 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['postid'] = (int)preg_replace("/[^0-9]/", '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['timestamp'] = time();
|
||||
$thumbnailUri = $element->find('img', 0)->src;
|
||||
$item['tags'] = $this->getTags($element);
|
||||
@@ -57,11 +57,80 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getFullURI())
|
||||
$content = getContents($this->getFullURI())
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
|
||||
$html = Fix_Simple_Html_Dom::str_get_html($content);
|
||||
|
||||
foreach($html->find(static::PATHTODATA) as $element) {
|
||||
$this->items[] = $this->getItemFromElement($element);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This class is a monkey patch to 'extend' simplehtmldom to recognize <source>
|
||||
* tags (HTML5) as self closing tag. This patch should be removed once
|
||||
* simplehtmldom was fixed. This seems to be a issue with more tags:
|
||||
* https://sourceforge.net/p/simplehtmldom/bugs/83/
|
||||
*
|
||||
* The tag itself is valid according to Mozilla:
|
||||
*
|
||||
* The HTML <picture> element serves as a container for zero or more <source>
|
||||
* elements and one <img> element to provide versions of an image for different
|
||||
* display device scenarios. The browser will consider each of the child <source>
|
||||
* elements and select one corresponding to the best match found; if no matches
|
||||
* are found among the <source> elements, the file specified by the <img>
|
||||
* element's src attribute is selected. The selected image is then presented in
|
||||
* the space occupied by the <img> element.
|
||||
*
|
||||
* -- https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture
|
||||
*
|
||||
* Notice: This class uses parts of the original simplehtmldom, adjusted to pass
|
||||
* the guidelines of RSS-Bridge (formatting)
|
||||
*/
|
||||
final class Fix_Simple_Html_Dom extends simple_html_dom {
|
||||
|
||||
/* copy from simple_html_dom, added 'source' at the end */
|
||||
protected $self_closing_tags = array(
|
||||
'img' => 1,
|
||||
'br' => 1,
|
||||
'input' => 1,
|
||||
'meta' => 1,
|
||||
'link' => 1,
|
||||
'hr' => 1,
|
||||
'base' => 1,
|
||||
'embed' => 1,
|
||||
'spacer' => 1,
|
||||
'source' => 1
|
||||
);
|
||||
|
||||
/* copy from simplehtmldom, changed 'simple_html_dom' to 'Fix_Simple_Html_Dom' */
|
||||
public static function str_get_html($str,
|
||||
$lowercase = true,
|
||||
$forceTagsClosed = true,
|
||||
$target_charset = DEFAULT_TARGET_CHARSET,
|
||||
$stripRN = true,
|
||||
$defaultBRText = DEFAULT_BR_TEXT,
|
||||
$defaultSpanText = DEFAULT_SPAN_TEXT)
|
||||
{
|
||||
$dom = new Fix_Simple_Html_Dom(null,
|
||||
$lowercase,
|
||||
$forceTagsClosed,
|
||||
$target_charset,
|
||||
$stripRN,
|
||||
$defaultBRText,
|
||||
$defaultSpanText);
|
||||
|
||||
if (empty($str) || strlen($str) > MAX_FILE_SIZE) {
|
||||
|
||||
$dom->clear();
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
$dom->load($str, $lowercase, $stripRN);
|
||||
|
||||
return $dom;
|
||||
}
|
||||
}
|
||||
|
@@ -15,8 +15,13 @@ class DansTonChatBridge extends BridgeAbstract {
|
||||
foreach($html->find('div.item') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['title'] = 'DansTonChat ' . $element->find('a', 1)->plaintext;
|
||||
$item['content'] = $element->find('a', 0)->innertext;
|
||||
$titleContent = $element->find('h3 a', 0);
|
||||
if($titleContent) {
|
||||
$item['title'] = 'DansTonChat ' . html_entity_decode($titleContent->plaintext, ENT_QUOTES);
|
||||
} else {
|
||||
$item['title'] = 'DansTonChat';
|
||||
}
|
||||
$item['content'] = $element->find('div.item-content a', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class DauphineLibereBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'qwertygc';
|
||||
const NAME = 'Dauphine Bridge';
|
||||
const URI = 'http://www.ledauphine.com/';
|
||||
const URI = 'https://www.ledauphine.com/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
@@ -49,8 +49,9 @@ class DauphineLibereBridge extends FeedExpander {
|
||||
|
||||
private function extractContent($url){
|
||||
$html2 = getSimpleHTMLDOMCached($url);
|
||||
$text = $html2->find('div.column', 0)->innertext;
|
||||
$text = preg_replace('@<script[^>]*?>.*?</script>@si', '', $text);
|
||||
return $text;
|
||||
foreach ($html2->find('.noprint, link, script, iframe, .shareTool, .contentInfo') as $remove) {
|
||||
$remove->outertext = '';
|
||||
}
|
||||
return $html2->find('div.content', 0)->innertext;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -35,11 +35,11 @@ class DemoBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
|
||||
$item = array();
|
||||
$item['author'] = "Me!";
|
||||
$item['title'] = "Test";
|
||||
$item['content'] = "Awesome content !";
|
||||
$item['id'] = "Lalala";
|
||||
$item['uri'] = "http://example.com/test";
|
||||
$item['author'] = 'Me!';
|
||||
$item['title'] = 'Test';
|
||||
$item['content'] = 'Awesome content !';
|
||||
$item['id'] = 'Lalala';
|
||||
$item['uri'] = 'http://example.com/test';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -4,143 +4,166 @@ class DemonoidBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'metaMMA';
|
||||
const NAME = 'Demonoid';
|
||||
const URI = 'https://www.demonoid.pw/';
|
||||
const DESCRIPTION = 'Returns results for the keywords (in all categories or
|
||||
a specific category). You can put several keywords separated by a semicolon
|
||||
(e.g. "one show;another show"). Searches can by done in a specific category;
|
||||
category number must be specified. (All=0, Movies=1, Music=2, TV=3, Games=4,
|
||||
Applications=5, Pictures=8, Anime=9, Comics=10, Books=11 Music Videos=8,
|
||||
Audio Books=17). User feed takes the Uploader ID number (not uploader name)
|
||||
as keyword. Uploader ID is found by clicking on uploader, clicking on
|
||||
"View this user\'s torrents", and copying the number after "uid=". An entire
|
||||
category feed is accomplished by leaving "keywords" box blank and using the
|
||||
corresponding category number.';
|
||||
const DESCRIPTION = 'Returns results from search';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'q' => array(
|
||||
'name' => 'keywords/user ID/category, separated by semicolons',
|
||||
'exampleValue' => 'first list;second list;…',
|
||||
'required' => true
|
||||
),
|
||||
'crit' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Feed type',
|
||||
'values' => array(
|
||||
'search' => 'search',
|
||||
'category' => 'cat',
|
||||
'user' => 'usr'
|
||||
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
|
||||
)
|
||||
)
|
||||
)
|
||||
),
|
||||
'catCheck' => array(
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Specify category for keyword search ?',
|
||||
),
|
||||
'cat' => array(
|
||||
'name' => 'Category number',
|
||||
),
|
||||
));
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$catBool = $this->getInput('catCheck');
|
||||
if($catBool) {
|
||||
$catNum = $this->getInput('cat');
|
||||
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 !');
|
||||
}
|
||||
$critList = $this->getInput('crit');
|
||||
|
||||
$keywordsList = explode(';', $this->getInput('q'));
|
||||
foreach($keywordsList as $keywords) {
|
||||
switch($critList) {
|
||||
case 'search':
|
||||
if($catBool == false) {
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?category=0&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($keywords) . #not rawurlencode so space -> '+'
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
} else {
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?category=' .
|
||||
rawurlencode($catNum) .
|
||||
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($keywords) . #not rawurlencode so space -> '+'
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
}
|
||||
break;
|
||||
case 'usr':
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?uid=' .
|
||||
rawurlencode($keywords) .
|
||||
'&seeded=2'
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
break;
|
||||
case 'cat':
|
||||
$html = file_get_contents(
|
||||
self::URI .
|
||||
'files/?uid=0&category=' .
|
||||
rawurlencode($keywords) .
|
||||
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
break;
|
||||
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;
|
||||
}
|
||||
|
||||
if(preg_match('~No torrents found~', $html)) {
|
||||
returnServerError('No result for query ' . $keywords);
|
||||
}
|
||||
|
||||
$bigTable = explode('<!-- start torrent list -->', $html)[1];
|
||||
$last50 = explode('<!-- end torrent list -->', $bigTable)[0];
|
||||
$dateChunk = explode('added_today', $last50);
|
||||
$item = array ();
|
||||
|
||||
for($block = 1;$block < count($dateChunk);$block++) {
|
||||
preg_match('~(?<=>Add).*?(?=<)~', $dateChunk[$block], $dateStr);
|
||||
$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+~', $dateStr[0], $fullDateStr);
|
||||
} 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']);
|
||||
}
|
||||
|
||||
$itemsChunk = explode('<!-- tstart -->', $dateChunk[$block]);
|
||||
|
||||
for($items = 1;$items < count($itemsChunk);$items++) {
|
||||
$item = array();
|
||||
$cols = explode('<td', $itemsChunk[$items]);
|
||||
preg_match('~(?<=href=\"/).*?(?=\")~', $cols[1], $matches);
|
||||
$item['id'] = self::URI . $matches[0];
|
||||
preg_match('~(?<=href=\").*?(?=\")~', $cols[4], $matches);
|
||||
$item['uri'] = $matches[0];
|
||||
$item['timestamp'] = $timestamp;
|
||||
preg_match('~(?<=href=\"/users/).*?(?=\")~', $cols[3], $matches);
|
||||
$item['author'] = $matches[0];
|
||||
preg_match('~(?<=/\">).*?(?=</a>)~', $cols[1], $matches);
|
||||
$item['title'] = $matches[0];
|
||||
preg_match('~(?<=green\">)\d+(?=</font>)~', $cols[8], $matches);
|
||||
$item['seeders'] = $matches[0];
|
||||
preg_match('~(?<=red\">)\d+(?=</font>)~', $cols[9], $matches);
|
||||
$item['leechers'] = $matches[0];
|
||||
preg_match('~(?<=>).*?(?=</td>)~', $cols[5], $matches);
|
||||
$item['size'] = $matches[0];
|
||||
$item['content'] = 'Uploaded by ' . $item['author']
|
||||
. ' , Size ' . $item['size']
|
||||
. '<br>seeders: '
|
||||
. $item['seeders']
|
||||
. ' | leechers: '
|
||||
. $item['leechers']
|
||||
. '<br><a href="'
|
||||
. $item['id']
|
||||
. '">info page</a>';
|
||||
if(isset($item['title']))
|
||||
$this->items[] = $item;
|
||||
}
|
||||
$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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
113
bridges/DerpibooruBridge.php
Normal file
113
bridges/DerpibooruBridge.php
Normal file
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
class DerpibooruBridge extends BridgeAbstract {
|
||||
const NAME = 'Derpibooru Bridge';
|
||||
const URI = 'https://derpibooru.org/';
|
||||
const DESCRIPTION = 'Returns newest posts from a Derpibooru search';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const MAINTAINER = 'Roliga';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'f' => array(
|
||||
'name' => 'Filter',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Everything' => 56027,
|
||||
'18+ R34' => 37432,
|
||||
'Legacy Default' => 37431,
|
||||
'18+ Dark' => 37429,
|
||||
'Maximum Spoilers' => 37430,
|
||||
'Default' => 100073
|
||||
),
|
||||
'defaultValue' => 56027
|
||||
|
||||
),
|
||||
'q' => array(
|
||||
'name' => 'Query',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function detectParameters($url){
|
||||
$params = array();
|
||||
|
||||
// Search page e.g. https://derpibooru.org/search?q=cute
|
||||
$regex = '/^(https?:\/\/)?(www\.)?derpibooru.org\/search.+q=([^\/&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['q'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Tag page, e.g. https://derpibooru.org/tags/artist-colon-devinian
|
||||
$regex = '/^(https?:\/\/)?(www\.)?derpibooru.org\/tags\/([^\/&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['q'] = str_replace('-colon-', ':', urldecode($matches[3]));
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return 'Derpibooru search for: '
|
||||
. $this->getInput('q');
|
||||
} else {
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('f')) && !is_null($this->getInput('q'))) {
|
||||
return self::URI
|
||||
. 'search?filter_id='
|
||||
. urlencode($this->getInput('f'))
|
||||
. '&q='
|
||||
. urlencode($this->getInput('q'));
|
||||
} else {
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$queryJson = json_decode(getContents(
|
||||
self::URI
|
||||
. 'search.json?filter_id='
|
||||
. urlencode($this->getInput('f'))
|
||||
. '&q='
|
||||
. urlencode($this->getInput('q'))
|
||||
)) or returnServerError('Failed to query Derpibooru');
|
||||
|
||||
foreach($queryJson->search as $post) {
|
||||
$item = array();
|
||||
|
||||
$postUri = self::URI . $post->id;
|
||||
|
||||
$item['uri'] = $postUri;
|
||||
$item['title'] = $post->id;
|
||||
$item['timestamp'] = strtotime($post->created_at);
|
||||
$item['author'] = $post->uploader;
|
||||
$item['enclosures'] = array('https:' . $post->image);
|
||||
$item['categories'] = explode(', ', $post->tags);
|
||||
|
||||
$item['content'] = '<p><a href="' // image preview
|
||||
. $postUri
|
||||
. '"><img src="https:'
|
||||
. $post->representations->medium
|
||||
. '"></a></p><p>' // description
|
||||
. $post->description
|
||||
. '</p><p><b>Size:</b> ' // image size
|
||||
. $post->width
|
||||
. 'x'
|
||||
. $post->height
|
||||
. '<br><b>Source:</b> <a href="' // source link
|
||||
. $post->source_url
|
||||
. '">'
|
||||
. $post->source_url
|
||||
. '</a></p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
236
bridges/DesoutterBridge.php
Normal file
236
bridges/DesoutterBridge.php
Normal file
@@ -0,0 +1,236 @@
|
||||
<?php
|
||||
class DesoutterBridge extends BridgeAbstract {
|
||||
|
||||
const CATEGORY_NEWS = 'News & Events';
|
||||
const CATEGORY_INDUSTRY = 'Industry 4.0 News';
|
||||
|
||||
const NAME = 'Desoutter Bridge';
|
||||
const URI = 'https://www.desouttertools.com';
|
||||
const DESCRIPTION = 'Returns feeds for news from Desoutter';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 86400; // 24 hours
|
||||
|
||||
const PARAMETERS = array(
|
||||
self::CATEGORY_NEWS => array(
|
||||
'news_lang' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'title' => 'Select your language',
|
||||
'defaultValue' => 'Corporate',
|
||||
'values' => array(
|
||||
'Corporate'
|
||||
=> 'https://www.desouttertools.com/about-desoutter/news-events',
|
||||
'Česko'
|
||||
=> 'https://www.desouttertools.cz/o-desoutter/aktuality-udalsoti',
|
||||
'Deutschland'
|
||||
=> 'https://www.desoutter.de/ueber-desoutter/news-events',
|
||||
'España'
|
||||
=> 'https://www.desouttertools.es/sobre-desoutter/noticias-eventos',
|
||||
'México'
|
||||
=> 'https://www.desouttertools.mx/acerca-desoutter/noticias-eventos',
|
||||
'France'
|
||||
=> 'https://www.desouttertools.fr/a-propos-de-desoutter/actualites-evenements',
|
||||
'Magyarország'
|
||||
=> 'https://www.desouttertools.hu/a-desoutter-vallalatrol/hirek-esemenyek',
|
||||
'Italia'
|
||||
=> 'https://www.desouttertools.it/su-desoutter/news-eventi',
|
||||
'日本'
|
||||
=> 'https://www.desouttertools.jp/desotanituite/niyusu-ibento',
|
||||
'대한민국'
|
||||
=> 'https://www.desouttertools.co.kr/desoteoe-daehaeseo/nyuseu-mic-ibenteu',
|
||||
'Polska'
|
||||
=> 'https://www.desouttertools.pl/o-desoutter/aktualnosci-wydarzenia',
|
||||
'Brasil'
|
||||
=> 'https://www.desouttertools.com.br/sobre-desoutter/noti%C2%ADcias-eventos',
|
||||
'Portugal'
|
||||
=> 'https://www.desouttertools.pt/sobre-desoutter/notIcias-eventos',
|
||||
'România'
|
||||
=> 'https://www.desouttertools.ro/despre-desoutter/noutati-evenimente',
|
||||
'Российская Федерация'
|
||||
=> 'https://www.desouttertools.com.ru/o-desoutter/novosti-mieropriiatiia',
|
||||
'Slovensko'
|
||||
=> 'https://www.desouttertools.sk/o-spolocnosti-desoutter/novinky-udalosti',
|
||||
'Slovenija'
|
||||
=> 'https://www.desouttertools.si/o-druzbi-desoutter/novice-dogodki',
|
||||
'Sverige'
|
||||
=> 'https://www.desouttertools.se/om-desoutter/nyheter-evenemang',
|
||||
'Türkiye'
|
||||
=> 'https://www.desoutter.com.tr/desoutter-hakkinda/haberler-etkinlikler',
|
||||
'中国'
|
||||
=> 'https://www.desouttertools.com.cn/guan-yu-ma-tou/xin-wen-he-huo-dong',
|
||||
)
|
||||
),
|
||||
),
|
||||
self::CATEGORY_INDUSTRY => array(
|
||||
'industry_lang' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'title' => 'Select your language',
|
||||
'defaultValue' => 'Corporate',
|
||||
'values' => array(
|
||||
'Corporate'
|
||||
=> 'https://www.desouttertools.com/industry-4-0/news',
|
||||
'Česko'
|
||||
=> 'https://www.desouttertools.cz/prumysl-4-0/novinky',
|
||||
'Deutschland'
|
||||
=> 'https://www.desoutter.de/industrie-4-0/news',
|
||||
'España'
|
||||
=> 'https://www.desouttertools.es/industria-4-0/noticias',
|
||||
'México'
|
||||
=> 'https://www.desouttertools.mx/industria-4-0/noticias',
|
||||
'France'
|
||||
=> 'https://www.desouttertools.fr/industrie-4-0/actualites',
|
||||
'Magyarország'
|
||||
=> 'https://www.desouttertools.hu/industry-4-0/hirek',
|
||||
'Italia'
|
||||
=> 'https://www.desouttertools.it/industry-4-0/news',
|
||||
'日本'
|
||||
=> 'https://www.desouttertools.jp/industry-4-0/news',
|
||||
'대한민국'
|
||||
=> 'https://www.desouttertools.co.kr/industry-4-0/news',
|
||||
'Polska'
|
||||
=> 'https://www.desouttertools.pl/przemysl-4-0/wiadomosci',
|
||||
'Brasil'
|
||||
=> 'https://www.desouttertools.com.br/industria-4-0/noticias',
|
||||
'Portugal'
|
||||
=> 'https://www.desouttertools.pt/industria-4-0/noticias',
|
||||
'România'
|
||||
=> 'https://www.desouttertools.ro/industry-4-0/noutati',
|
||||
'Российская Федерация'
|
||||
=> 'https://www.desouttertools.com.ru/industry-4-0/news',
|
||||
'Slovensko'
|
||||
=> 'https://www.desouttertools.sk/priemysel-4-0/novinky',
|
||||
'Slovenija'
|
||||
=> 'https://www.desouttertools.si/industrija-4-0/novice',
|
||||
'Sverige'
|
||||
=> 'https://www.desouttertools.se/industri-4-0/nyheter',
|
||||
'Türkiye'
|
||||
=> 'https://www.desoutter.com.tr/endustri-4-0/haberler',
|
||||
'中国'
|
||||
=> 'https://www.desouttertools.com.cn/industry-4-0/news',
|
||||
)
|
||||
),
|
||||
),
|
||||
'global' => array(
|
||||
'full' => array(
|
||||
'name' => 'Load full articles',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Enable to load the full article for each item'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $title;
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case self::CATEGORY_NEWS:
|
||||
return $this->getInput('news_lang') ?: parent::getURI();
|
||||
case self::CATEGORY_INDUSTRY:
|
||||
return $this->getInput('industry_lang') ?: parent::getURI();
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return isset($this->title) ? $this->title . ' - ' . parent::getName() : parent::getName();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
// Uncomment to generate list of languages automtically (dev mode)
|
||||
/*
|
||||
switch($this->queriedContext) {
|
||||
case self::CATEGORY_NEWS:
|
||||
$this->extractNewsLanguages(); die;
|
||||
case self::CATEGORY_INDUSTRY:
|
||||
$this->extractIndustryLanguages(); die;
|
||||
}
|
||||
*/
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$this->title = html_entity_decode($html->find('title', 0)->plaintext, ENT_QUOTES);
|
||||
|
||||
foreach($html->find('article') as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('[itemprop="name"]', 0)->href;
|
||||
$item['title'] = $article->find('[itemprop="name"]', 0)->title;
|
||||
|
||||
if($this->getInput('full')) {
|
||||
$item['content'] = $this->getFullNewsArticle($item['uri']);
|
||||
} else {
|
||||
$item['content'] = $article->find('[itemprop="description"]', 0)->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function getFullNewsArticle($uri) {
|
||||
$html = getSimpleHTMLDOMCached($uri)
|
||||
or returnServerError('Unable to load full article!');
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
return $html->find('section.article', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a HTML page with a PHP formatted array of languages,
|
||||
* pointing to the corresponding news pages. Implementation is based
|
||||
* on the 'Corporate' site.
|
||||
* @return void
|
||||
*/
|
||||
private function extractNewsLanguages() {
|
||||
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/about-desoutter/news-events')
|
||||
or returnServerError('Error loading news!');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$items = $html->find('ul[class="dropdown-menu"] li');
|
||||
|
||||
$list = "\t'Corporate'\n\t=> 'https://www.desouttertools.com/about-desoutter/news-events',\n";
|
||||
|
||||
foreach($items as $item) {
|
||||
$lang = trim($item->plaintext);
|
||||
$uri = $item->find('a', 0)->href;
|
||||
|
||||
$list .= "\t'{$lang}'\n\t=> '{$uri}',\n";
|
||||
}
|
||||
|
||||
echo $list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a HTML page with a PHP formatted array of languages,
|
||||
* pointing to the corresponding news pages. Implementation is based
|
||||
* on the 'Corporate' site.
|
||||
* @return void
|
||||
*/
|
||||
private function extractIndustryLanguages() {
|
||||
$html = getSimpleHTMLDOMCached('https://www.desouttertools.com/industry-4-0/news')
|
||||
or returnServerError('Error loading news!');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$items = $html->find('ul[class="dropdown-menu"] li');
|
||||
|
||||
$list = "\t'Corporate'\n\t=> 'https://www.desouttertools.com/industry-4-0/news',\n";
|
||||
|
||||
foreach($items as $item) {
|
||||
$lang = trim($item->plaintext);
|
||||
$uri = $item->find('a', 0)->href;
|
||||
|
||||
$list .= "\t'{$lang}'\n\t=> '{$uri}',\n";
|
||||
}
|
||||
|
||||
echo $list;
|
||||
}
|
||||
}
|
103
bridges/DevToBridge.php
Normal file
103
bridges/DevToBridge.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
class DevToBridge extends BridgeAbstract {
|
||||
|
||||
const CONTEXT_BY_TAG = 'By tag';
|
||||
|
||||
const NAME = 'dev.to Bridge';
|
||||
const URI = 'https://dev.to';
|
||||
const DESCRIPTION = 'Returns feeds for tags';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 10800; // 15 min.
|
||||
|
||||
const PARAMETERS = array(
|
||||
self::CONTEXT_BY_TAG => array(
|
||||
'tag' => array(
|
||||
'name' => 'Tag',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert your tag',
|
||||
'exampleValue' => 'python'
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full article',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Enable to receive the full article for each item'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case self::CONTEXT_BY_TAG:
|
||||
if($tag = $this->getInput('tag')) {
|
||||
return static::URI . '/t/' . urlencode($tag);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://practicaldev-herokuapp-com.freetls.fastly.net/assets/
|
||||
apple-icon-5c6fa9f2bce280428589c6195b7f1924206a53b782b371cfe2d02da932c8c173.png';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOMCached($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$articles = $html->find('div[class="single-article"]')
|
||||
or returnServerError('Could not find articles!');
|
||||
|
||||
foreach($articles as $article) {
|
||||
|
||||
if($article->find('[class*="cta"]', 0)) { // Skip ads
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('a[id*=article-link]', 0)->href;
|
||||
$item['title'] = $article->find('h3', 0)->plaintext;
|
||||
|
||||
// i.e. "Charlie Harrington・Sep 21"
|
||||
$item['timestamp'] = strtotime(explode('・', $article->find('h4 a', 0)->plaintext, 2)[1]);
|
||||
$item['author'] = explode('・', $article->find('h4 a', 0)->plaintext, 2)[0];
|
||||
|
||||
// Profile image
|
||||
$item['enclosures'] = array($article->find('img', 0)->src);
|
||||
|
||||
if($this->getInput('full')) {
|
||||
$fullArticle = $this->getFullArticle($item['uri']);
|
||||
$item['content'] = <<<EOD
|
||||
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
|
||||
<p>{$fullArticle}</p>
|
||||
EOD;
|
||||
} else {
|
||||
$item['content'] = <<<EOD
|
||||
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
|
||||
<p>{$item['title']}</p>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$item['categories'] = array_map(function($e){ return $e->plaintext; }, $article->find('div.tags span.tag'));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function getFullArticle($url) {
|
||||
$html = getSimpleHTMLDOMCached($url)
|
||||
or returnServerError('Unable to load article from "' . $url . '"!');
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
return $html->find('[id="article-body"]', 0);
|
||||
}
|
||||
}
|
@@ -75,6 +75,10 @@ class DiceBridge extends BridgeAbstract {
|
||||
),
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://assets.dice.com/techpro/img/favicons/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.dice.com/jobs/advancedResult.html';
|
||||
$uri .= '?for_one=' . urlencode($this->getInput('for_one'));
|
||||
|
@@ -3,23 +3,23 @@ class DilbertBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'kranack';
|
||||
const NAME = 'Dilbert Daily Strip';
|
||||
const URI = 'http://dilbert.com';
|
||||
const URI = 'https://dilbert.com';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'The Unofficial Dilbert Daily Comic Strip';
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request Dilbert: ' . $this->getURI());
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request Dilbert: ' . self::URI);
|
||||
|
||||
foreach($html->find('section.comic-item') as $element) {
|
||||
|
||||
$img = $element->find('img', 0);
|
||||
$link = $element->find('a', 0);
|
||||
$comic = $img->src;
|
||||
$title = $link->alt;
|
||||
$title = $img->alt;
|
||||
$url = $link->href;
|
||||
$date = substr($url, 25);
|
||||
$date = substr(strrchr($url, '/'), 1);
|
||||
if (empty($title))
|
||||
$title = 'Dilbert Comic Strip on ' . $date;
|
||||
$date = strtotime($date);
|
||||
|
116
bridges/DiscogsBridge.php
Normal file
116
bridges/DiscogsBridge.php
Normal file
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
class DiscogsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'DiscogsBridge';
|
||||
const URI = 'https://www.discogs.com/';
|
||||
const DESCRIPTION = 'Returns releases from discogs';
|
||||
const PARAMETERS = array(
|
||||
'Artist Releases' => array(
|
||||
'artistid' => array(
|
||||
'name' => 'Artist ID',
|
||||
'type' => 'number',
|
||||
)
|
||||
),
|
||||
'Label Releases' => array(
|
||||
'labelid' => array(
|
||||
'name' => 'Label ID',
|
||||
'type' => 'number',
|
||||
)
|
||||
),
|
||||
'User Wantlist' => array(
|
||||
'username_wantlist' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
)
|
||||
),
|
||||
'User Folder' => array(
|
||||
'username_folder' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
),
|
||||
'folderid' => array(
|
||||
'name' => 'Folder ID',
|
||||
'type' => 'number',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(!empty($this->getInput('artistid')) || !empty($this->getInput('labelid'))) {
|
||||
|
||||
if(!empty($this->getInput('artistid'))) {
|
||||
$data = getContents('https://api.discogs.com/artists/'
|
||||
. $this->getInput('artistid')
|
||||
. '/releases?sort=year&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
} elseif(!empty($this->getInput('labelid'))) {
|
||||
$data = getContents('https://api.discogs.com/labels/'
|
||||
. $this->getInput('labelid')
|
||||
. '/releases?sort=year&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
}
|
||||
|
||||
$jsonData = json_decode($data, true);
|
||||
foreach($jsonData['releases'] as $release) {
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $release['artist'];
|
||||
$item['title'] = $release['title'];
|
||||
$item['id'] = $release['id'];
|
||||
$resId = array_key_exists('main_release', $release) ? $release['main_release'] : $release['id'];
|
||||
$item['uri'] = self::URI . $this->getInput('artistid') . '/release/' . $resId;
|
||||
|
||||
if(isset($release['year'])) {
|
||||
$item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp();
|
||||
}
|
||||
|
||||
$item['content'] = $item['author'] . ' - ' . $item['title'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
} elseif(!empty($this->getInput('username_wantlist')) || !empty($this->getInput('username_folder'))) {
|
||||
|
||||
if(!empty($this->getInput('username_wantlist'))) {
|
||||
$data = getContents('https://api.discogs.com/users/'
|
||||
. $this->getInput('username_wantlist')
|
||||
. '/wants?sort=added&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
$jsonData = json_decode($data, true)['wants'];
|
||||
|
||||
} elseif(!empty($this->getInput('username_folder'))) {
|
||||
$data = getContents('https://api.discogs.com/users/'
|
||||
. $this->getInput('username_folder')
|
||||
. '/collection/folders/'
|
||||
. $this->getInput('folderid')
|
||||
. '/releases?sort=added&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
$jsonData = json_decode($data, true)['releases'];
|
||||
}
|
||||
foreach($jsonData as $element) {
|
||||
|
||||
$infos = $element['basic_information'];
|
||||
$item = array();
|
||||
$item['title'] = $infos['title'];
|
||||
$item['author'] = $infos['artists'][0]['name'];
|
||||
$item['id'] = $infos['artists'][0]['id'];
|
||||
$item['uri'] = self::URI . $infos['artists'][0]['id'] . '/release/' . $infos['id'];
|
||||
$item['timestamp'] = strtotime($element['date_added']);
|
||||
$item['content'] = $item['author'] . ' - ' . $item['title'];
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return static::NAME;
|
||||
}
|
||||
}
|
@@ -7,6 +7,11 @@ class DribbbleBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
const DESCRIPTION = 'Returns the newest popular shots from Dribbble.';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.dribbble.com/assets/
|
||||
favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . '/shots')
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
161
bridges/ETTVBridge.php
Normal file
161
bridges/ETTVBridge.php
Normal file
@@ -0,0 +1,161 @@
|
||||
<?php
|
||||
class ETTVBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'GregThib';
|
||||
const NAME = 'ETTV';
|
||||
const URI = 'https://www.ettv.tv/';
|
||||
const DESCRIPTION = 'Returns list of 20 latest torrents for a specific search.';
|
||||
const CACHE_TIMEOUT = 14400; // 4 hours
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'query' => array(
|
||||
'name' => 'Keywords',
|
||||
'required' => true
|
||||
),
|
||||
'cat' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Category',
|
||||
'values' => array(
|
||||
'(ALL TYPES)' => '0',
|
||||
'Anime: Movies' => '73',
|
||||
'Anime: Dubbed/Subbed' => '74',
|
||||
'Anime: Others' => '75',
|
||||
'Books: Ebooks' => '53',
|
||||
'Books: Magazines' => '54',
|
||||
'Books: Comics' => '55',
|
||||
'Books: Audio' => '56',
|
||||
'Books: Others' => '68',
|
||||
'Games: Windows' => '57',
|
||||
'Games: Android' => '58',
|
||||
'Games: Others' => '71',
|
||||
'Movies: HD 1080p' => '1',
|
||||
'Movies: HD 720p' => '2',
|
||||
'Movies: UltraHD/4K' => '3',
|
||||
'Movies: XviD' => '42',
|
||||
'Movies: X264/H264' => '47',
|
||||
'Movies: 3D' => '49',
|
||||
'Movies: Dubs/Dual Audio' => '51',
|
||||
'Movies: CAM/TS' => '65',
|
||||
'Movies: BluRay Disc/Remux' => '66',
|
||||
'Movies: DVDR' => '67',
|
||||
'Movies: HEVC/x265' => '76',
|
||||
'Music: MP3' => '59',
|
||||
'Music: FLAC' => '60',
|
||||
'Music: Music Videos' => '61',
|
||||
'Music: Others' => '69',
|
||||
'Software: Windows' => '62',
|
||||
'Software: Android' => '63',
|
||||
'Software: Mac' => '64',
|
||||
'Software: Others' => '70',
|
||||
'TV: HD/X264/H264' => '41',
|
||||
'TV: SD/X264/H264' => '5',
|
||||
'TV: TV Packs' => '7',
|
||||
'TV: SD/XVID' => '50',
|
||||
'TV: Sport' => '72',
|
||||
'TV: HEVC/x265' => '77',
|
||||
'Unsorted: Unsorted' => '78'
|
||||
),
|
||||
'defaultValue' => '(ALL TYPES)'
|
||||
),
|
||||
'status' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Status',
|
||||
'values' => array(
|
||||
'Active Transfers' => '0',
|
||||
'Included Dead' => '1',
|
||||
'Only Dead' => '2'
|
||||
),
|
||||
'defaultValue' => 'Included Dead'
|
||||
),
|
||||
'lang' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Lang',
|
||||
'values' => array(
|
||||
'(ALL)' => '0',
|
||||
'Arabic' => '17',
|
||||
'Chinese ' => '10',
|
||||
'Danish' => '13',
|
||||
'Dutch' => '11',
|
||||
'English' => '1',
|
||||
'Finnish' => '18',
|
||||
'French' => '2',
|
||||
'German' => '3',
|
||||
'Greek' => '15',
|
||||
'Hindi' => '8',
|
||||
'Italian' => '4',
|
||||
'Japanese' => '5',
|
||||
'Korean' => '9',
|
||||
'Polish' => '14',
|
||||
'Russian' => '7',
|
||||
'Spanish' => '6',
|
||||
'Turkish' => '16'
|
||||
),
|
||||
'defaultValue' => '(ALL)'
|
||||
)
|
||||
));
|
||||
|
||||
protected $results_link;
|
||||
|
||||
public function collectData(){
|
||||
// No control on inputs, because all defaultValue are set
|
||||
$query_str = 'torrents-search.php';
|
||||
$query_str .= '?search=' . urlencode('+' . str_replace(' ', ' +', $this->getInput('query')));
|
||||
$query_str .= '&cat=' . $this->getInput('cat');
|
||||
$query_str .= '&incldead=' . $this->getInput('status');
|
||||
$query_str .= '&lang=' . $this->getInput('lang');
|
||||
$query_str .= '&sort=id&order=desc';
|
||||
|
||||
// Get results page
|
||||
$this->results_link = self::URI . $query_str;
|
||||
$html = getSimpleHTMLDOM($this->results_link)
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
|
||||
// Loop on each entry
|
||||
foreach($html->find('table.table tr') as $element) {
|
||||
if($element->parent->tag == 'thead') continue;
|
||||
$entry = $element->find('td', 1)->find('a', 0);
|
||||
|
||||
// retrieve result page to get more details
|
||||
$link = rtrim(self::URI, '/') . $entry->href;
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request page ' . $link);
|
||||
|
||||
// get details & download links
|
||||
$details = $page->find('fieldset.download table', 0); // WHAT?? It should be the second one…
|
||||
$dllinks = $page->find('div#downloadbox table', 0);
|
||||
|
||||
// fill item
|
||||
$item = array();
|
||||
$item['author'] = $details->children(6)->children(1)->plaintext;
|
||||
$item['title'] = $entry->title;
|
||||
$item['uri'] = $link;
|
||||
$item['timestamp'] = strtotime($details->children(7)->children(1)->plaintext);
|
||||
$item['content'] = '';
|
||||
$item['content'] .= '<br/><b>Name: </b>' . $details->children(0)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Lang: </b>' . $details->children(3)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Size: </b>' . $details->children(4)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Hash: </b>' . $details->children(5)->children(1)->innertext;
|
||||
foreach($dllinks->children(0)->children(1)->find('a') as $dl) {
|
||||
$item['content'] .= '<br/>' . $dl->outertext;
|
||||
}
|
||||
$item['content'] .= '<br/><br/>' . $details->children(1)->children(0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if($this->getInput('query')) {
|
||||
return '[' . self::NAME . '] ' . $this->getInput('query');
|
||||
}
|
||||
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(isset($this->results_link) && !empty($this->results_link)) {
|
||||
return $this->results_link;
|
||||
}
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class EZTVBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = "alexAubin";
|
||||
const MAINTAINER = 'alexAubin';
|
||||
const NAME = 'EZTV';
|
||||
const URI = 'https://eztv.ch/';
|
||||
const DESCRIPTION = 'Returns list of *recent* torrents for a specific show
|
||||
@@ -23,15 +23,15 @@ on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.';
|
||||
$relativeDays = 0;
|
||||
$relativeHours = 0;
|
||||
|
||||
foreach(explode(" ", $relativeReleaseTime) as $relativeTimeElement) {
|
||||
if(substr($relativeTimeElement, -1) == "d") $relativeDays = substr($relativeTimeElement, 0, -1);
|
||||
if(substr($relativeTimeElement, -1) == "h") $relativeHours = substr($relativeTimeElement, 0, -1);
|
||||
foreach(explode(' ', $relativeReleaseTime) as $relativeTimeElement) {
|
||||
if(substr($relativeTimeElement, -1) == 'd') $relativeDays = substr($relativeTimeElement, 0, -1);
|
||||
if(substr($relativeTimeElement, -1) == 'h') $relativeHours = substr($relativeTimeElement, 0, -1);
|
||||
}
|
||||
return mktime(date('h') - $relativeHours, 0, 0, date('m'), date('d') - $relativeDays, date('Y'));
|
||||
}
|
||||
|
||||
// Loop on show ids
|
||||
$showList = explode(",", $this->getInput('i'));
|
||||
$showList = explode(',', $this->getInput('i'));
|
||||
foreach($showList as $showID) {
|
||||
|
||||
// Get show 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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,20 +6,36 @@ class EliteDangerousGalnetBridge extends BridgeAbstract {
|
||||
const URI = 'https://community.elitedangerous.com/galnet/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the latest page of news from Galnet';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'French' => 'fr',
|
||||
'German' => 'de'
|
||||
),
|
||||
'defaultValue' => 'en'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
$language = $this->getInput('language');
|
||||
$url = 'https://community.elitedangerous.com/';
|
||||
$url = $url . $language . '/galnet';
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
foreach($html->find('div.article') as $element) {
|
||||
$item = array();
|
||||
|
||||
$uri = $element->find('h3 a', 0)->href;
|
||||
$uri = self::URI . substr($uri, strlen('/galnet/'));
|
||||
$uri = 'https://community.elitedangerous.com/' . $language . $uri;
|
||||
$item['uri'] = $uri;
|
||||
|
||||
$title = $element->find('h3 a', 0)->plaintext;
|
||||
$item['title'] = substr($title, 1); //remove the space between icon and title
|
||||
$item['title'] = $element->find('h3 a', 0)->plaintext;
|
||||
|
||||
$content = $element->find('p', -1)->innertext;
|
||||
$item['content'] = $content;
|
||||
|
146
bridges/ElloBridge.php
Normal file
146
bridges/ElloBridge.php
Normal file
@@ -0,0 +1,146 @@
|
||||
<?php
|
||||
class ElloBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Ello Bridge';
|
||||
const URI = 'https://ello.co/';
|
||||
const CACHE_TIMEOUT = 4800; //2hours
|
||||
const DESCRIPTION = 'Returns the newest posts for Ello';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'By User' => array(
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'title' => 'Username'
|
||||
)
|
||||
),
|
||||
'Search' => array(
|
||||
's' => array(
|
||||
'name' => 'Search',
|
||||
'required' => true,
|
||||
'title' => 'Search'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . $this->getAPIKey()
|
||||
);
|
||||
|
||||
if(!empty($this->getInput('u'))) {
|
||||
$postData = getContents(self::URI . 'api/v2/users/~' . urlencode($this->getInput('u')) . '/posts', $header) or
|
||||
returnServerError('Unable to query Ello API.');
|
||||
} else {
|
||||
$postData = getContents(self::URI . 'api/v2/posts?terms=' . urlencode($this->getInput('s')), $header) or
|
||||
returnServerError('Unable to query Ello API.');
|
||||
}
|
||||
|
||||
$postData = json_decode($postData);
|
||||
$count = 0;
|
||||
foreach($postData->posts as $post) {
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $this->getUsername($post, $postData);
|
||||
$item['timestamp'] = strtotime($post->created_at);
|
||||
$item['title'] = strip_tags($this->findText($post->summary));
|
||||
$item['content'] = $this->getPostContent($post->body);
|
||||
$item['enclosures'] = $this->getEnclosures($post, $postData);
|
||||
$item['uri'] = self::URI . $item['author'] . '/post/' . $post->token;
|
||||
$content = $post->body;
|
||||
|
||||
$this->items[] = $item;
|
||||
$count += 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function findText($path) {
|
||||
|
||||
foreach($path as $summaryElement) {
|
||||
|
||||
if($summaryElement->kind == 'text') {
|
||||
return $summaryElement->data;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
private function getPostContent($path) {
|
||||
|
||||
$content = '';
|
||||
foreach($path as $summaryElement) {
|
||||
|
||||
if($summaryElement->kind == 'text') {
|
||||
$content .= $summaryElement->data;
|
||||
} elseif ($summaryElement->kind == 'image') {
|
||||
$alt = '';
|
||||
if(property_exists($summaryElement->data, 'alt')) {
|
||||
$alt = $summaryElement->data->alt;
|
||||
}
|
||||
$content .= '<img src="' . $summaryElement->data->url . '" alt="' . $alt . '" />';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $content;
|
||||
|
||||
}
|
||||
|
||||
private function getEnclosures($post, $postData) {
|
||||
|
||||
$assets = [];
|
||||
foreach($post->links->assets as $asset) {
|
||||
foreach($postData->linked->assets as $assetLink) {
|
||||
if($asset == $assetLink->id) {
|
||||
$assets[] = $assetLink->attachment->original->url;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $assets;
|
||||
|
||||
}
|
||||
|
||||
private function getUsername($post, $postData) {
|
||||
|
||||
foreach($postData->linked->users as $user) {
|
||||
if($user->id == $post->links->author->id) {
|
||||
return $user->username;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function getAPIKey() {
|
||||
$cache = Cache::create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope(get_called_class());
|
||||
$cache->setKey(['key']);
|
||||
$key = $cache->loadData();
|
||||
|
||||
if($key == null) {
|
||||
$keyInfo = getContents(self::URI . 'api/webapp-token') or
|
||||
returnServerError('Unable to get token.');
|
||||
$key = json_decode($keyInfo)->token->access_token;
|
||||
$cache->saveData($key);
|
||||
}
|
||||
|
||||
return $key;
|
||||
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return $this->getInput('u') . ' - Ello Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -57,6 +57,10 @@ class ElsevierBridge extends BridgeAbstract {
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.elsevier.io/verona/includes/favicons/favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
|
@@ -7,19 +7,9 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Should we put a website in production today? (French)';
|
||||
|
||||
public function collectData(){
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request EstCeQuonMetEnProd: ' . $this->getURI());
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request EstCeQuonMetEnProd: ' . self::URI);
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '#' . date('Y-m-d');
|
||||
@@ -28,8 +18,8 @@ class EstCeQuonMetEnProdBridge extends BridgeAbstract {
|
||||
$item['timestamp'] = strtotime('today midnight');
|
||||
$item['content'] = str_replace(
|
||||
'src="/',
|
||||
'src="' . $this->getURI(),
|
||||
trim(extractFromDelimiters($html->outertext, '<body role="document">', '<br /><br />'))
|
||||
'src="' . self::URI,
|
||||
trim(extractFromDelimiters($html->outertext, '<body role="document">', '<div id="share'))
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
@@ -17,7 +17,7 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'queryextension' => array(
|
||||
'name' => 'Query extension',
|
||||
'type' => 'text',
|
||||
'requied' => false,
|
||||
'required' => false,
|
||||
'title' => 'Insert additional query parts here
|
||||
(anything after ?search=<your search query>)',
|
||||
'exampleValue' => '&explicit=1&locationQuery=2921044'
|
||||
@@ -25,9 +25,9 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'showimage' => array(
|
||||
'name' => 'Show image in content',
|
||||
'type' => 'checkbox',
|
||||
'requrired' => false,
|
||||
'required' => false,
|
||||
'title' => 'Activate to show the image in the content',
|
||||
'defaultValue' => false
|
||||
'defaultValue' => 'checked'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -36,26 +36,27 @@ class EtsyBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed to receive ' . $this->getURI());
|
||||
|
||||
$results = $html->find('div.block-grid-item');
|
||||
$results = $html->find('li.block-grid-item');
|
||||
|
||||
foreach($results as $result) {
|
||||
// Skip banner cards (ads for categories)
|
||||
if($result->find('a.banner-card'))
|
||||
if($result->find('span.ad-indicator'))
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $result->find('a', 0)->title;
|
||||
$item['uri'] = $result->find('a', 0)->href;
|
||||
$item['author'] = $result->find('div.card-shop-name', 0)->plaintext;
|
||||
$item['author'] = $result->find('p.text-gray-lighter', 0)->plaintext;
|
||||
|
||||
$item['content'] = '<p>'
|
||||
. $result->find('div.card-price', 0)->plaintext
|
||||
. $result->find('span.currency-value', 0)->plaintext . ' '
|
||||
. $result->find('span.currency-symbol', 0)->plaintext
|
||||
. '</p><p>'
|
||||
. $result->find('div.card-title', 0)->plaintext
|
||||
. $result->find('a', 0)->title
|
||||
. '</p>';
|
||||
|
||||
$image = $result->find('img.placeholder', 0)->src;
|
||||
$image = $result->find('img.display-block', 0)->src;
|
||||
|
||||
if($this->getInput('showimage')) {
|
||||
$item['content'] .= '<img src="' . $image . '">';
|
||||
|
102
bridges/ExtremeDownloadBridge.php
Normal file
102
bridges/ExtremeDownloadBridge.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?php
|
||||
class ExtremeDownloadBridge extends BridgeAbstract {
|
||||
const NAME = 'Extreme Download';
|
||||
const URI = 'https://ww1.extreme-d0wn.com/';
|
||||
const DESCRIPTION = 'Suivi de série sur Extreme Download';
|
||||
const MAINTAINER = 'sysadminstory';
|
||||
const PARAMETERS = array(
|
||||
'Suivre la publication des épisodes d\'une série en cours de diffusion' => array(
|
||||
'url' => array(
|
||||
'name' => 'URL de la série',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une série sans le https://ww1.extreme-d0wn.com/',
|
||||
'exampleValue' => 'series-hd/hd-series-vostfr/46631-halt-and-catch-fire-saison-04-vostfr-hdtv-720p.html'),
|
||||
'filter' => array(
|
||||
'name' => 'Type de contenu',
|
||||
'type' => 'list',
|
||||
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
|
||||
'values' => array(
|
||||
'Streaming et Téléchargement' => 'both',
|
||||
'Téléchargement' => 'download',
|
||||
'Streaming' => 'streaming'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . $this->getInput('url'))
|
||||
or returnServerError('Could not request Extreme Download.');
|
||||
|
||||
$filter = $this->getInput('filter');
|
||||
|
||||
$typesText = array(
|
||||
'download' => 'Téléchargement',
|
||||
'streaming' => 'Streaming'
|
||||
);
|
||||
|
||||
// Get the TV show title
|
||||
$this->showTitle = trim($html->find('span[id=news-title]', 0)->plaintext);
|
||||
|
||||
$list = $html->find('div[class=prez_7]');
|
||||
foreach($list as $element) {
|
||||
$add = false;
|
||||
// Link type is needed is needed to generate an unique link
|
||||
$type = $this->findLinkType($element);
|
||||
if($filter == 'both') {
|
||||
$add = true;
|
||||
} else {
|
||||
if($type == $filter) {
|
||||
$add = true;
|
||||
}
|
||||
}
|
||||
if($add == true) {
|
||||
$item = array();
|
||||
|
||||
// Get the element name
|
||||
$title = $element->plaintext;
|
||||
|
||||
// Get thee element links
|
||||
$links = $element->next_sibling()->innertext;
|
||||
|
||||
$item['content'] = $links;
|
||||
$item['title'] = $this->showTitle . ' ' . $title . ' - ' . $typesText[$type];
|
||||
// As RSS Bridge use the URI as GUID they need to be unique : adding a md5 hash of the title element
|
||||
// should geneerate unique URI to prevent confusion for RSS readers
|
||||
$item['uri'] = self::URI . $this->getInput('url') . '#' . hash('md5', $item['title']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
|
||||
return $this->showTitle . ' - ' . self::NAME;
|
||||
break;
|
||||
default:
|
||||
return self::NAME;
|
||||
}
|
||||
}
|
||||
|
||||
private function findLinkType($element)
|
||||
{
|
||||
$return = '';
|
||||
// Walk through all elements in the reverse order until finding one with class 'presz_2'
|
||||
while($element->class != 'prez_2') {
|
||||
$element = $element->prev_sibling();
|
||||
}
|
||||
$text = html_entity_decode($element->plaintext);
|
||||
|
||||
// Regarding the text of the element, return the according link type
|
||||
if(stristr($text, 'téléchargement') != false) {
|
||||
$return = 'download';
|
||||
} else if(stristr($text, 'streaming') != false) {
|
||||
$return = 'streaming';
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
}
|
@@ -15,24 +15,18 @@ class FB2Bridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Utility function for cleaning a Facebook link
|
||||
$unescape_fb_link = function($matches){
|
||||
if(is_array($matches) && count($matches) > 1) {
|
||||
$link = $matches[1];
|
||||
if(strpos($link, '/') === 0)
|
||||
$link = self::URI . $link . '"';
|
||||
$link = self::URI . substr($link, 1);
|
||||
if(strpos($link, 'facebook.com/l.php?u=') !== false)
|
||||
$link = urldecode(extractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
|
||||
return ' href="' . $link . '"';
|
||||
@@ -75,51 +69,63 @@ class FB2Bridge extends BridgeAbstract {
|
||||
if($this->getInput('u') !== null) {
|
||||
$page = 'https://touch.facebook.com/' . $this->getInput('u');
|
||||
$cookies = $this->getCookies($page);
|
||||
$pageID = $this->getPageID($page, $cookies);
|
||||
$pageInfo = $this->getPageInfos($page, $cookies);
|
||||
|
||||
if($pageID === null) {
|
||||
echo <<<EOD
|
||||
if($pageInfo['userId'] === null) {
|
||||
returnClientError(<<<EOD
|
||||
Unable to get the page id. You should consider getting the ID by hand, then importing it into FB2Bridge
|
||||
EOD;
|
||||
die();
|
||||
} elseif($pageID == -1) {
|
||||
echo <<<EOD
|
||||
EOD
|
||||
);
|
||||
} elseif($pageInfo['userId'] == -1) {
|
||||
returnClientError(<<<EOD
|
||||
This page is not accessible without being logged in.
|
||||
EOD;
|
||||
die();
|
||||
EOD
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Build the string for the first request
|
||||
$requestString = 'https://touch.facebook.com/pages_reaction_units/more/?page_id='
|
||||
. $pageID
|
||||
. '&cursor={"card_id"%3A"videos"%2C"has_next_page"%3Atrue}&surface=mobile_page_home&unit_count=8';
|
||||
|
||||
$fileContent = file_get_contents($requestString);
|
||||
|
||||
$articleIndex = 0;
|
||||
$maxArticle = 3;
|
||||
|
||||
$requestString = 'https://touch.facebook.com/page_content_list_view/more/?page_id='
|
||||
. $pageInfo['userId']
|
||||
. '&start_cursor=1&num_to_fetch=105&surface_type=timeline';
|
||||
$fileContent = getContents($requestString);
|
||||
$html = $this->buildContent($fileContent);
|
||||
$author = $this->getInput('u');
|
||||
$author = $pageInfo['username'];
|
||||
|
||||
foreach($html->find("article") as $content) {
|
||||
foreach($html->find('article') as $content) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = "http://touch.facebook.com"
|
||||
. $content->find("div[class='_52jc _5qc4 _24u0 _36xo']", 0)->find("a", 0)->getAttribute("href");
|
||||
preg_match('/publish_time\\\":([0-9]+),/', $content->getAttribute('data-store', 0), $match);
|
||||
if(isset($match[1]))
|
||||
$timestamp = $match[1];
|
||||
else
|
||||
$timestamp = 0;
|
||||
|
||||
if($content->find("header", 0) !== null) {
|
||||
$content->find("header", 0)->innertext = "";
|
||||
$item['uri'] = html_entity_decode('http://touch.facebook.com'
|
||||
. $content->find("div[class='_52jc _5qc4 _78cz _24u0 _36xo']", 0)->find('a', 0)->getAttribute('href'), ENT_QUOTES);
|
||||
|
||||
//Decode images
|
||||
$imagecleaned = preg_replace_callback('/<i [^>]* style="[^"]*url\(\'(.*?)\'\).*?><\/i>/m', function ($matches) {
|
||||
return "<img src='" . str_replace(['\\3a ', '\\3d ', '\\26 '], [':', '=', '&'], $matches[1]) . "' />";
|
||||
}, $content);
|
||||
$content = str_get_html($imagecleaned);
|
||||
|
||||
if($content->find('header', 0) !== null) {
|
||||
$content->find('header', 0)->innertext = '';
|
||||
}
|
||||
|
||||
if($content->find("footer", 0) !== null) {
|
||||
$content->find("footer", 0)->innertext = "";
|
||||
if($content->find('footer', 0) !== null) {
|
||||
$content->find('footer', 0)->innertext = '';
|
||||
}
|
||||
|
||||
// Replace emoticon images by their textual representation (part of the span)
|
||||
foreach($content->find('span[title*="emoticon"]') as $emoticon) {
|
||||
$emoticon->innertext = $emoticon->find('span[aria-hidden="true"]', 0)->innertext;
|
||||
}
|
||||
|
||||
//Remove html nodes, keep only img, links, basic formatting
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p>');
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p><h3><h4><section>');
|
||||
|
||||
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
|
||||
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
|
||||
@@ -132,7 +138,6 @@ EOD;
|
||||
'ajaxify',
|
||||
'tabindex',
|
||||
'class',
|
||||
'style',
|
||||
'data-[^=]*',
|
||||
'aria-[^=]*',
|
||||
'role',
|
||||
@@ -145,7 +150,36 @@ EOD;
|
||||
// "<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
|
||||
$content = preg_replace_callback('/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i', $unescape_fb_emote, $content);
|
||||
|
||||
$item['content'] = $content;
|
||||
//Remove the "...Plus" tag
|
||||
$content = preg_replace(
|
||||
'/… (<span>|)<a href="https:\/\/www\.facebook\.com\/story\.php\?story_fbid=.*?<\/a>/m',
|
||||
'', $content, 1);
|
||||
|
||||
//Remove tracking images
|
||||
$content = preg_replace('/<img src=\'.*?safe_image\.php.*?\' \/>/m', '', $content);
|
||||
|
||||
//Remove the double section tags
|
||||
$content = str_replace(['<section><section>', '</section></section>'], ['<section>', '</section>'], $content);
|
||||
|
||||
//Move the section tag link upper, if it is down
|
||||
$content = str_get_html($content);
|
||||
$sectionContent = $content->find('section', 0);
|
||||
if($sectionContent != null) {
|
||||
$sectionLink = $sectionContent->nextSibling();
|
||||
if($sectionLink != null) {
|
||||
$fullLink = '<a href="' . $sectionLink->getAttribute('href') . '">' . $sectionContent->innertext . '</a>';
|
||||
$sectionContent->innertext = $fullLink;
|
||||
}
|
||||
}
|
||||
|
||||
//Move the href tag upper if it is inside the section
|
||||
foreach($content->find('section > a') as $sectionToFix) {
|
||||
$sectionLink = $sectionToFix->getAttribute('href');
|
||||
$section = $sectionToFix->parent();
|
||||
$section->outertext = '<a href="' . $sectionLink . '">' . $section . '</a>';
|
||||
}
|
||||
|
||||
$item['content'] = html_entity_decode($content, ENT_QUOTES);
|
||||
|
||||
$title = $author;
|
||||
if (strlen($title) > 24)
|
||||
@@ -154,59 +188,29 @@ EOD;
|
||||
if (strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES);
|
||||
$item['author'] = html_entity_decode($author, ENT_QUOTES);
|
||||
$item['timestamp'] = html_entity_decode($timestamp, ENT_QUOTES);
|
||||
|
||||
array_push($this->items, $item);
|
||||
if($item['timestamp'] != 0)
|
||||
array_push($this->items, $item);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Currently not used. Is used to get more than only 3 elements, as they appear on another page.
|
||||
private function computeNextLink($string, $pageID){
|
||||
|
||||
$regex = implode(
|
||||
'',
|
||||
array(
|
||||
"/timeline_unit",
|
||||
"\\\\\\\\u00253A1",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)",
|
||||
"\\\\\\\\u00253A([0-9]*)/"
|
||||
)
|
||||
);
|
||||
|
||||
preg_match($regex, $string, $result);
|
||||
|
||||
return implode(
|
||||
'',
|
||||
array(
|
||||
"https://touch.facebook.com/pages_reaction_units/more/?page_id=",
|
||||
$pageID,
|
||||
"&cursor=%7B%22timeline_cursor%22%3A%22timeline_unit%3A1%3A",
|
||||
$result[1],
|
||||
"%3A",
|
||||
$result[2],
|
||||
"%3A",
|
||||
$result[3],
|
||||
"%3A",
|
||||
$result[4],
|
||||
"%22%2C%22timeline_section_cursor%22%3A%7B%7D%2C%22",
|
||||
"has_next_page%22%3Atrue%7D&surface=mobile_page_home&unit_count=3"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
//Builds the HTML from the encoded JS that Facebook provides.
|
||||
private function buildContent($pageContent){
|
||||
|
||||
$regex = "/\\\"html\\\":\\\"(.*?)\\\",\\\"replace/";
|
||||
// The html ends with:
|
||||
// /div>","replaceifexists
|
||||
$regex = '/\\"html\\":(\".+\/div>"),"replace/';
|
||||
preg_match($regex, $pageContent, $result);
|
||||
|
||||
return str_get_html(html_entity_decode(json_decode('"' . $result[1] . '"')));
|
||||
}
|
||||
$htmlContent = json_decode($result[1]);
|
||||
$htmlContent = preg_replace('/(?<!style)="(.*?)"/', '=\'$1\'', $htmlContent);
|
||||
$htmlContent = html_entity_decode($htmlContent, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
return str_get_html($htmlContent);
|
||||
}
|
||||
|
||||
//Builds the cookie from the page, as Facebook sometimes refuses to give
|
||||
//the page if no cookie is provided.
|
||||
@@ -214,7 +218,7 @@ EOD;
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'http' => array(
|
||||
'user_agent' => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0",
|
||||
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
||||
)
|
||||
)
|
||||
@@ -222,24 +226,24 @@ EOD;
|
||||
$a = file_get_contents($pageURL, 0, $ctx);
|
||||
|
||||
//First request to get the cookie
|
||||
$cookies = "";
|
||||
$cookies = '';
|
||||
foreach($http_response_header as $hdr) {
|
||||
if(strpos($hdr, "Set-Cookie") !== false) {
|
||||
$cLine = explode(":", $hdr)[1];
|
||||
$cLine = explode(";", $cLine)[0];
|
||||
$cookies .= ";" . $cLine;
|
||||
if(strpos($hdr, 'Set-Cookie') !== false) {
|
||||
$cLine = explode(':', $hdr)[1];
|
||||
$cLine = explode(';', $cLine)[0];
|
||||
$cookies .= ';' . $cLine;
|
||||
}
|
||||
}
|
||||
|
||||
return substr($cookies, 1);
|
||||
}
|
||||
|
||||
//Get the page ID from the Facebook page.
|
||||
private function getPageID($page, $cookies){
|
||||
//Get the page ID and username from the Facebook page.
|
||||
private function getPageInfos($page, $cookies){
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
'user_agent' => "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0",
|
||||
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||
'header' => 'Cookie: ' . $cookies
|
||||
)
|
||||
)
|
||||
@@ -247,23 +251,32 @@ EOD;
|
||||
|
||||
$pageContent = file_get_contents($page, 0, $context);
|
||||
|
||||
if(strpos($pageContent, "signup-button") != false) {
|
||||
if(strpos($pageContent, 'signup-button') != false) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
//Get the username
|
||||
$usernameRegex = '/data-nt=\"FB:TEXT4\">(.*?)<\/div>/m';
|
||||
preg_match($usernameRegex, $pageContent, $usernameMatches);
|
||||
if(count($usernameMatches) > 0) {
|
||||
$username = strip_tags($usernameMatches[1]);
|
||||
} else {
|
||||
$username = $this->getInput('u');
|
||||
}
|
||||
|
||||
//Get the page ID if we don't have a captcha
|
||||
$regex = "/page_id=([0-9]*)&/";
|
||||
$regex = '/page_id=([0-9]*)&/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
if(count($matches) > 0) {
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
}
|
||||
|
||||
//Get the page ID if we do have a captcha
|
||||
$regex = "/\"pageID\":\"([0-9]*)\"/";
|
||||
$regex = '/"pageID":"([0-9]*)"/';
|
||||
preg_match($regex, $pageContent, $matches);
|
||||
|
||||
return $matches[1];
|
||||
return array('userId' => $matches[1], 'username' => $username);
|
||||
|
||||
}
|
||||
|
||||
@@ -274,8 +287,4 @@ EOD;
|
||||
public function getURI(){
|
||||
return 'http://facebook.com';
|
||||
}
|
||||
|
||||
public function getCacheDuration(){
|
||||
return 60 * 60 * 3; // 5 minutes
|
||||
}
|
||||
}
|
||||
|
57
bridges/FDroidBridge.php
Normal file
57
bridges/FDroidBridge.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class FDroidBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Mitsukarenai';
|
||||
const NAME = 'F-Droid Bridge';
|
||||
const URI = 'https://f-droid.org/';
|
||||
const CACHE_TIMEOUT = 60 * 60 * 2; // 2 hours
|
||||
const DESCRIPTION = 'Returns latest added/updated apps on the open-source Android apps repository F-Droid';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'u' => array(
|
||||
'name' => 'Widget selection',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Latest added apps' => 'added',
|
||||
'Latest updated apps' => 'updated'
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/favicon.ico?v=8j6PKzW9Mk';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI;
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request F-Droid.');
|
||||
|
||||
// targetting the corresponding widget based on user selection
|
||||
// "updated" is the 5th widget on the page, "added" is the 6th
|
||||
|
||||
switch($this->getInput('u')) {
|
||||
case 'updated':
|
||||
$html_widget = $html->find('div.sidebar-widget', 5);
|
||||
break;
|
||||
default:
|
||||
$html_widget = $html->find('div.sidebar-widget', 6);
|
||||
break;
|
||||
}
|
||||
|
||||
// and now extracting app info from the selected widget (and yeah turns out icons are of heterogeneous sizes)
|
||||
|
||||
foreach($html_widget->find('a') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $element->href;
|
||||
$item['title'] = $element->find('h4', 0)->plaintext;
|
||||
$item['icon'] = $element->find('img', 0)->src;
|
||||
$item['summary'] = $element->find('span.package-summary', 0)->plaintext;
|
||||
$item['content'] = '
|
||||
<a href="' . $item['uri'] . '">
|
||||
<img alt="" style="max-height:128px" src="' . $item['icon'] . '">
|
||||
</a><br>' . $item['summary'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,190 +1,544 @@
|
||||
<?php
|
||||
class FacebookBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Facebook';
|
||||
const MAINTAINER = 'teromene, logmanoriginal';
|
||||
const NAME = 'Facebook Bridge';
|
||||
const URI = 'https://www.facebook.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5min
|
||||
const DESCRIPTION = 'Input a page title or a profile log. For a profile log,
|
||||
please insert the parameter as follow : myExamplePage/132621766841117';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true
|
||||
),
|
||||
'media_type' => array(
|
||||
'name' => 'Media type',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Video' => 'video',
|
||||
'No Video' => 'novideo'
|
||||
const PARAMETERS = array(
|
||||
'User' => array(
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
'media_type' => array(
|
||||
'name' => 'Media type',
|
||||
'type' => 'list',
|
||||
'required' => false,
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Video' => 'video',
|
||||
'No Video' => 'novideo'
|
||||
),
|
||||
'defaultValue' => 'all'
|
||||
),
|
||||
'skip_reviews' => array(
|
||||
'name' => 'Skip reviews',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'defaultValue' => false,
|
||||
'title' => 'Feed includes reviews when checked'
|
||||
)
|
||||
),
|
||||
'Group' => array(
|
||||
'g' => array(
|
||||
'name' => 'Group',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://www.facebook.com/groups/743149642484225',
|
||||
'title' => 'Insert group name or facebook group URL'
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'title' => 'Specify the number of items to return (default: -1)',
|
||||
'defaultValue' => -1
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
private $authorName = '';
|
||||
private $groupName = '';
|
||||
|
||||
public function collectData(){
|
||||
public function getIcon() {
|
||||
return 'https://static.xx.fbcdn.net/rsrc.php/yo/r/iRmz9lCMBD2.ico';
|
||||
}
|
||||
|
||||
//Extract a string using start and end delimiters
|
||||
function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
public function getName(){
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'User':
|
||||
if(!empty($this->authorName)) {
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||
. ' - ' . static::NAME;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Group':
|
||||
if(!empty($this->groupName)) {
|
||||
return $this->groupName . ' - ' . static::NAME;
|
||||
}
|
||||
break;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//Utility function for cleaning a Facebook link
|
||||
$unescape_fb_link = function($matches){
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = self::URI;
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'Group':
|
||||
// Discover groups via https://www.facebook.com/groups/
|
||||
// Example group: https://www.facebook.com/groups/sailors.worldwide
|
||||
$uri .= 'groups/' . $this->sanitizeGroup(filter_var($this->getInput('g'), FILTER_SANITIZE_URL));
|
||||
break;
|
||||
|
||||
case 'User':
|
||||
// Example user 1: https://www.facebook.com/artetv/
|
||||
// Example user 2: artetv
|
||||
$user = $this->sanitizeUser($this->getInput('u'));
|
||||
|
||||
if(!strpos($user, '/')) {
|
||||
$uri .= urlencode($user) . '/posts';
|
||||
} else {
|
||||
$uri .= 'pages/' . $user;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
// Request the mobile version to reduce page size (no javascript)
|
||||
// More information: https://stackoverflow.com/a/11103592
|
||||
return $uri .= '?_fb_noscript=1';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'Group':
|
||||
$this->collectGroupData();
|
||||
break;
|
||||
|
||||
case 'User':
|
||||
$this->collectUserData();
|
||||
break;
|
||||
|
||||
default:
|
||||
returnClientError('Unknown context: "' . $this->queriedContext . '"!');
|
||||
|
||||
}
|
||||
|
||||
$limit = $this->getInput('limit') ?: -1;
|
||||
|
||||
if($limit > 0 && count($this->items) > $limit) {
|
||||
$this->items = array_slice($this->items, 0, $limit);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#region Group
|
||||
|
||||
private function collectGroupData() {
|
||||
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n");
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('Failed loading facebook page: ' . $this->getURI());
|
||||
|
||||
if(!$this->isPublicGroup($html)) {
|
||||
returnClientError('This group is not public! RSS-Bridge only supports public groups!');
|
||||
}
|
||||
|
||||
defaultLinkTo($html, substr(self::URI, 0, strlen(self::URI) - 1));
|
||||
|
||||
$this->groupName = $this->extractGroupName($html);
|
||||
|
||||
$posts = $html->find('div.userContentWrapper')
|
||||
or returnServerError('Failed finding posts!');
|
||||
|
||||
foreach($posts as $post) {
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->extractGroupURI($post);
|
||||
$item['title'] = $this->extractGroupTitle($post);
|
||||
$item['author'] = $this->extractGroupAuthor($post);
|
||||
$item['content'] = $this->extractGroupContent($post);
|
||||
$item['timestamp'] = $this->extractGroupTimestamp($post);
|
||||
$item['enclosures'] = $this->extractGroupEnclosures($post);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function sanitizeGroup($group) {
|
||||
|
||||
if(filter_var(
|
||||
$group,
|
||||
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
|
||||
// User provided a URL
|
||||
|
||||
$urlparts = parse_url($group);
|
||||
|
||||
if($urlparts['host'] !== parse_url(self::URI)['host']
|
||||
&& 'www.' . $urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $urlparts['host']
|
||||
. '", expected "'
|
||||
. parse_url(self::URI)['host']
|
||||
. '"!');
|
||||
|
||||
}
|
||||
|
||||
return explode('/', $urlparts['path'])[2];
|
||||
|
||||
} elseif(strpos($group, '/') !== false) {
|
||||
returnClientError('The group you provided is invalid: ' . $group);
|
||||
} else {
|
||||
return $group;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function isPublicGroup($html) {
|
||||
|
||||
// Facebook redirects to the groups about page for non-public groups
|
||||
$about = $html->find('#pagelet_group_about', 0);
|
||||
|
||||
return !($about);
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupName($html) {
|
||||
|
||||
$ogtitle = $html->find('meta[property="og:title"]', 0)
|
||||
or returnServerError('Unable to find group title!');
|
||||
|
||||
return html_entity_decode($ogtitle->content, ENT_QUOTES);
|
||||
}
|
||||
|
||||
private function extractGroupURI($post) {
|
||||
|
||||
$elements = $post->find('a')
|
||||
or returnServerError('Unable to find URI!');
|
||||
|
||||
foreach($elements as $anchor) {
|
||||
|
||||
// Find the one that is a permalink
|
||||
if(strpos($anchor->href, 'permalink') !== false) {
|
||||
return $anchor->href;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupContent($post) {
|
||||
|
||||
$content = $post->find('div.userContent', 0)
|
||||
or returnServerError('Unable to find user content!');
|
||||
|
||||
return $content->innertext . $content->next_sibling()->innertext;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupTimestamp($post) {
|
||||
|
||||
$element = $post->find('abbr[data-utime]', 0)
|
||||
or returnServerError('Unable to find timestamp!');
|
||||
|
||||
return $element->getAttribute('data-utime');
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupAuthor($post) {
|
||||
|
||||
$element = $post->find('img', 0)
|
||||
or returnServerError('Unable to find author information!');
|
||||
|
||||
return $element->{'aria-label'};
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupEnclosures($post) {
|
||||
|
||||
$elements = $post->find('div.userContent', 0)->next_sibling()->find('img');
|
||||
|
||||
$enclosures = array();
|
||||
|
||||
foreach($elements as $enclosure) {
|
||||
$enclosures[] = $enclosure->src;
|
||||
}
|
||||
|
||||
return empty($enclosures) ? null : $enclosures;
|
||||
|
||||
}
|
||||
|
||||
private function extractGroupTitle($post) {
|
||||
|
||||
$element = $post->find('h5', 0)
|
||||
or returnServerError('Unable to find title!');
|
||||
|
||||
if(strpos($element->plaintext, 'shared') === false) {
|
||||
|
||||
$content = strip_tags($this->extractGroupContent($post));
|
||||
|
||||
return $this->extractGroupAuthor($post)
|
||||
. ' posted: '
|
||||
. substr(
|
||||
$content,
|
||||
0,
|
||||
strpos(wordwrap($content, 64), "\n")
|
||||
)
|
||||
. '...';
|
||||
|
||||
}
|
||||
|
||||
return $element->plaintext;
|
||||
|
||||
}
|
||||
|
||||
#endregion (Group)
|
||||
|
||||
#region User
|
||||
|
||||
/**
|
||||
* Checks if $user is a valid username or URI and returns the username
|
||||
*/
|
||||
private function sanitizeUser($user) {
|
||||
if (filter_var($user, FILTER_VALIDATE_URL)) {
|
||||
|
||||
$urlparts = parse_url($user);
|
||||
|
||||
if($urlparts['host'] !== parse_url(self::URI)['host']) {
|
||||
returnClientError('The host you provided is invalid! Received "'
|
||||
. $urlparts['host']
|
||||
. '", expected "'
|
||||
. parse_url(self::URI)['host']
|
||||
. '"!');
|
||||
}
|
||||
|
||||
if(!array_key_exists('path', $urlparts)
|
||||
|| $urlparts['path'] === '/') {
|
||||
returnClientError('The URL you provided doesn\'t contain the user name!');
|
||||
}
|
||||
|
||||
return explode('/', $urlparts['path'])[1];
|
||||
|
||||
} else {
|
||||
|
||||
// First character cannot be a forward slash
|
||||
if(strpos($user, '/') === 0) {
|
||||
returnClientError('Remove leading slash "/" from the username!');
|
||||
}
|
||||
|
||||
return $user;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bypass external link redirection
|
||||
*/
|
||||
private function unescape_fb_link($content){
|
||||
return preg_replace_callback('/ href=\"([^"]+)\"/i', function($matches){
|
||||
if(is_array($matches) && count($matches) > 1) {
|
||||
|
||||
$link = $matches[1];
|
||||
if(strpos($link, '/') === 0)
|
||||
$link = self::URI . $link;
|
||||
|
||||
if(strpos($link, 'facebook.com/l.php?u=') !== false)
|
||||
$link = urldecode(extractFromDelimiters($link, 'facebook.com/l.php?u=', '&'));
|
||||
|
||||
return ' href="' . $link . '"';
|
||||
|
||||
}
|
||||
};
|
||||
}, $content);
|
||||
}
|
||||
|
||||
//Utility function for converting facebook emoticons
|
||||
$unescape_fb_emote = function($matches){
|
||||
static $facebook_emoticons = array(
|
||||
'smile' => ':)',
|
||||
'frown' => ':(',
|
||||
'tongue' => ':P',
|
||||
'grin' => ':D',
|
||||
'gasp' => ':O',
|
||||
'wink' => ';)',
|
||||
'pacman' => ':<',
|
||||
'grumpy' => '>_<',
|
||||
'unsure' => ':/',
|
||||
'cry' => ':\'(',
|
||||
'kiki' => '^_^',
|
||||
'glasses' => '8-)',
|
||||
'sunglasses' => 'B-)',
|
||||
'heart' => '<3',
|
||||
'devil' => ']:D',
|
||||
'angel' => '0:)',
|
||||
'squint' => '-_-',
|
||||
'confused' => 'o_O',
|
||||
'upset' => 'xD',
|
||||
'colonthree' => ':3',
|
||||
'like' => '👍');
|
||||
$len = count($matches);
|
||||
if ($len > 1)
|
||||
for ($i = 1; $i < $len; $i++)
|
||||
foreach ($facebook_emoticons as $name => $emote)
|
||||
if ($matches[$i] === $name)
|
||||
return $emote;
|
||||
return $matches[0];
|
||||
};
|
||||
/**
|
||||
* Remove Facebook's tracking code
|
||||
*/
|
||||
private function remove_tracking_codes($content){
|
||||
return preg_replace_callback('/ href=\"([^"]+)\"/i', function($matches){
|
||||
if(is_array($matches) && count($matches) > 1) {
|
||||
|
||||
$html = null;
|
||||
$link = $matches[1];
|
||||
|
||||
//Handle captcha response sent by the viewer
|
||||
if(strpos($link, 'facebook.com') !== false) {
|
||||
if(strpos($link, '?') !== false) {
|
||||
$link = substr($link, 0, strpos($link, '?'));
|
||||
}
|
||||
}
|
||||
return ' href="' . $link . '"';
|
||||
|
||||
}
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert textual representation of emoticons back to ASCII emoticons.
|
||||
* i.e. "<i><u>smile emoticon</u></i>" => ":)"
|
||||
*/
|
||||
private function unescape_fb_emote($content){
|
||||
return preg_replace_callback('/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i', function($matches){
|
||||
static $facebook_emoticons = array(
|
||||
'smile' => ':)',
|
||||
'frown' => ':(',
|
||||
'tongue' => ':P',
|
||||
'grin' => ':D',
|
||||
'gasp' => ':O',
|
||||
'wink' => ';)',
|
||||
'pacman' => ':<',
|
||||
'grumpy' => '>_<',
|
||||
'unsure' => ':/',
|
||||
'cry' => ':\'(',
|
||||
'kiki' => '^_^',
|
||||
'glasses' => '8-)',
|
||||
'sunglasses' => 'B-)',
|
||||
'heart' => '<3',
|
||||
'devil' => ']:D',
|
||||
'angel' => '0:)',
|
||||
'squint' => '-_-',
|
||||
'confused' => 'o_O',
|
||||
'upset' => 'xD',
|
||||
'colonthree' => ':3',
|
||||
'like' => '👍');
|
||||
|
||||
$len = count($matches);
|
||||
|
||||
if ($len > 1)
|
||||
for ($i = 1; $i < $len; $i++)
|
||||
foreach ($facebook_emoticons as $name => $emote)
|
||||
if ($matches[$i] === $name)
|
||||
return $emote;
|
||||
|
||||
return $matches[0];
|
||||
}, $content);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the captcha message for the given captcha
|
||||
*/
|
||||
private function returnCaptchaMessage($captcha) {
|
||||
// Save form for submitting after getting captcha response
|
||||
if (session_status() == PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
$captcha_fields = array();
|
||||
|
||||
foreach ($captcha->find('input, button') as $input) {
|
||||
$captcha_fields[$input->name] = $input->value;
|
||||
}
|
||||
|
||||
$_SESSION['captcha_fields'] = $captcha_fields;
|
||||
$_SESSION['captcha_action'] = $captcha->find('form', 0)->action;
|
||||
|
||||
// Show captcha filling form to the viewer, proxying the captcha image
|
||||
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
||||
|
||||
header('Content-Type: text/html', true, 500);
|
||||
|
||||
$message = <<<EOD
|
||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||
<h2>Facebook captcha challenge</h2>
|
||||
<p>Unfortunately, rss-bridge cannot fetch the requested page.<br />
|
||||
Facebook wants rss-bridge to resolve the following captcha:</p>
|
||||
<p><img src="data:image/png;base64,{$img}" /></p>
|
||||
<p><b>Response:</b> <input name="captcha_response" placeholder="please fill in" />
|
||||
<input type="submit" value="Submit!" /></p>
|
||||
</form>
|
||||
EOD;
|
||||
|
||||
die($message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a capture response was received and tries to load the contents
|
||||
* @return mixed null if no capture response was received, simplhtmldom document otherwise
|
||||
*/
|
||||
private function handleCaptchaResponse() {
|
||||
if (isset($_POST['captcha_response'])) {
|
||||
if (session_status() == PHP_SESSION_NONE)
|
||||
session_start();
|
||||
|
||||
if (isset($_SESSION['captcha_fields'], $_SESSION['captcha_action'])) {
|
||||
$captcha_action = $_SESSION['captcha_action'];
|
||||
$captcha_fields = $_SESSION['captcha_fields'];
|
||||
$captcha_fields['captcha_response'] = preg_replace("/[^a-zA-Z0-9]+/", "", $_POST['captcha_response']);
|
||||
$http_options = array(
|
||||
'http' => array(
|
||||
'method' => 'POST',
|
||||
'user_agent' => ini_get('user_agent'),
|
||||
'header' => array("Content-type:
|
||||
application/x-www-form-urlencoded\r\nReferer: $captcha_action\r\nCookie: noscript=1\r\n"),
|
||||
'content' => http_build_query($captcha_fields)
|
||||
),
|
||||
);
|
||||
$context = stream_context_create($http_options);
|
||||
$html = getContents($captcha_action, false, $context);
|
||||
$captcha_fields['captcha_response'] = preg_replace('/[^a-zA-Z0-9]+/', '', $_POST['captcha_response']);
|
||||
|
||||
if($html === false) {
|
||||
returnServerError('Failed to submit captcha response back to Facebook');
|
||||
}
|
||||
unset($_SESSION['captcha_fields']);
|
||||
$html = str_get_html($html);
|
||||
$header = array(
|
||||
'Content-type: application/x-www-form-urlencoded',
|
||||
'Referer: ' . $captcha_action,
|
||||
'Cookie: noscript=1'
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => http_build_query($captcha_fields)
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($captcha_action, $header, $opts)
|
||||
or returnServerError('Failed to submit captcha response back to Facebook');
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
unset($_SESSION['captcha_fields']);
|
||||
unset($_SESSION['captcha_action']);
|
||||
}
|
||||
|
||||
//Retrieve page contents
|
||||
return null;
|
||||
}
|
||||
|
||||
private function collectUserData(){
|
||||
|
||||
$html = $this->handleCaptchaResponse();
|
||||
|
||||
// Retrieve page contents
|
||||
if(is_null($html)) {
|
||||
$http_options = array(
|
||||
'http' => array(
|
||||
'method' => 'GET',
|
||||
'user_agent' => ini_get('user_agent'),
|
||||
'header' => 'Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE') . "\r\n"
|
||||
)
|
||||
);
|
||||
$context = stream_context_create($http_options);
|
||||
if(!strpos($this->getInput('u'), "/")) {
|
||||
$html = getSimpleHTMLDOM(self::URI . urlencode($this->getInput('u')) . '?_fb_noscript=1',
|
||||
false,
|
||||
$context)
|
||||
or returnServerError('No results for this query.');
|
||||
} else {
|
||||
$html = getSimpleHTMLDOM(self::URI . 'pages/' . $this->getInput('u') . '?_fb_noscript=1',
|
||||
false,
|
||||
$context)
|
||||
or returnServerError('No results for this query.');
|
||||
}
|
||||
|
||||
$header = array('Accept-Language: ' . getEnv('HTTP_ACCEPT_LANGUAGE'));
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(), $header)
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
}
|
||||
|
||||
//Handle captcha form?
|
||||
// Handle captcha form?
|
||||
$captcha = $html->find('div.captcha_interstitial', 0);
|
||||
if (!is_null($captcha)) {
|
||||
//Save form for submitting after getting captcha response
|
||||
if (session_status() == PHP_SESSION_NONE)
|
||||
session_start();
|
||||
$captcha_fields = array();
|
||||
foreach ($captcha->find('input, button') as $input)
|
||||
$captcha_fields[$input->name] = $input->value;
|
||||
$_SESSION['captcha_fields'] = $captcha_fields;
|
||||
$_SESSION['captcha_action'] = $captcha->find('form', 0)->action;
|
||||
|
||||
//Show captcha filling form to the viewer, proxying the captcha image
|
||||
$img = base64_encode(getContents($captcha->find('img', 0)->src));
|
||||
http_response_code(500);
|
||||
header('Content-Type: text/html');
|
||||
$message = <<<EOD
|
||||
<form method="post" action="?{$_SERVER['QUERY_STRING']}">
|
||||
<h2>Facebook captcha challenge</h2>
|
||||
<p>Unfortunately, rss-bridge cannot fetch the requested page.<br />
|
||||
Facebook wants rss-bridge to resolve the following captcha:</p>
|
||||
<p><img src="data:image/png;base64,{$img}" /></p>
|
||||
<p><b>Response:</b> <input name="captcha_response" placeholder="please fill in" />
|
||||
<input type="submit" value="Submit!" /></p>
|
||||
</form>
|
||||
EOD;
|
||||
die($message);
|
||||
if (!is_null($captcha)) {
|
||||
$this->returnCaptchaMessage($captcha);
|
||||
}
|
||||
|
||||
// No captcha? We can carry on retrieving page contents :)
|
||||
// First, we check wether the page is public or not
|
||||
$loginForm = $html->find('._585r', 0);
|
||||
|
||||
if($loginForm != null) {
|
||||
returnServerError('You must be logged in to view this page. This is not supported by RSS-Bridge.');
|
||||
}
|
||||
|
||||
//No captcha? We can carry on retrieving page contents :)
|
||||
$element = $html
|
||||
->find('#pagelet_timeline_main_column')[0]
|
||||
->children(0)
|
||||
->children(0)
|
||||
->children(0)
|
||||
->next_sibling()
|
||||
->children(0);
|
||||
|
||||
if(isset($element)) {
|
||||
|
||||
$author = str_replace(' | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
||||
$profilePic = 'https://graph.facebook.com/'
|
||||
. $this->getInput('u')
|
||||
. '/picture?width=200&height=200';
|
||||
$author = str_replace(' - Posts | Facebook', '', $html->find('title#pageTitle', 0)->innertext);
|
||||
|
||||
$profilePic = $html->find('meta[property="og:image"]', 0)->content;
|
||||
|
||||
$this->authorName = $author;
|
||||
|
||||
@@ -196,6 +550,12 @@ EOD;
|
||||
$posts = array($cell);
|
||||
}
|
||||
|
||||
// Optionally skip reviews
|
||||
if($this->getInput('skip_reviews')
|
||||
&& !is_null($cell->find('#review_composer_container', 0))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach($posts as $post) {
|
||||
// Check media type
|
||||
switch($this->getInput('media_type')) {
|
||||
@@ -213,16 +573,30 @@ EOD;
|
||||
|
||||
if(count($post->find('abbr')) > 0) {
|
||||
|
||||
//Retrieve post contents
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"clearfix([^>]+)>(.+?)div\ class=\"userContent\"/i',
|
||||
'',
|
||||
$post);
|
||||
$content = $post->find('.userContentWrapper', 0);
|
||||
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"_59tj([^>]+)>(.+?)<\/div><\/div><a/i',
|
||||
'',
|
||||
$content);
|
||||
// This array specifies filters applied to all posts in order of appearance
|
||||
$content_filters = array(
|
||||
'._5mly', // Remove embedded videos (the preview image remains)
|
||||
'._2ezg', // Remove "Views ..."
|
||||
'.hidden_elem', // Remove hidden elements (they are hidden anyway)
|
||||
);
|
||||
|
||||
foreach($content_filters as $filter) {
|
||||
foreach($content->find($filter) as $subject) {
|
||||
$subject->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
// Change origin tag for embedded media from div to paragraph
|
||||
foreach($content->find('._59tj') as $subject) {
|
||||
$subject->outertext = '<p>' . $subject->innertext . '</p>';
|
||||
}
|
||||
|
||||
// Change title tag for embedded media from anchor to paragraph
|
||||
foreach($content->find('._3n1k a') as $anchor) {
|
||||
$anchor->outertext = '<p>' . $anchor->innertext . '</p>';
|
||||
}
|
||||
|
||||
$content = preg_replace(
|
||||
'/(?i)><div class=\"_3dp([^>]+)>(.+?)div\ class=\"[^u]+userContent\"/i',
|
||||
@@ -234,13 +608,18 @@ EOD;
|
||||
'',
|
||||
$content);
|
||||
|
||||
//Remove html nodes, keep only img, links, basic formatting
|
||||
// Remove "SpSonsSoriSsés"
|
||||
$content = preg_replace(
|
||||
'/(?iU)<a [^>]+ href="#" role="link" [^>}]+>.+<\/a>/iU',
|
||||
'',
|
||||
$content);
|
||||
|
||||
// Remove html nodes, keep only img, links, basic formatting
|
||||
$content = strip_tags($content, '<a><img><i><u><br><p>');
|
||||
|
||||
//Adapt link hrefs: convert relative links into absolute links and bypass external link redirection
|
||||
$content = preg_replace_callback('/ href=\"([^"]+)\"/i', $unescape_fb_link, $content);
|
||||
$content = $this->unescape_fb_link($content);
|
||||
|
||||
//Clean useless html tag properties and fix link closing tags
|
||||
// Clean useless html tag properties and fix link closing tags
|
||||
foreach (array(
|
||||
'onmouseover',
|
||||
'onclick',
|
||||
@@ -253,55 +632,59 @@ EOD;
|
||||
'aria-[^=]*',
|
||||
'role',
|
||||
'rel',
|
||||
'id') as $property_name)
|
||||
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
|
||||
'id') as $property_name) {
|
||||
$content = preg_replace('/ ' . $property_name . '=\"[^"]*\"/i', '', $content);
|
||||
}
|
||||
|
||||
$content = preg_replace('/<\/a [^>]+>/i', '</a>', $content);
|
||||
|
||||
//Convert textual representation of emoticons eg
|
||||
//"<i><u>smile emoticon</u></i>" back to ASCII emoticons eg ":)"
|
||||
$content = preg_replace_callback(
|
||||
'/<i><u>([^ <>]+) ([^<>]+)<\/u><\/i>/i',
|
||||
$unescape_fb_emote,
|
||||
$content
|
||||
);
|
||||
$this->unescape_fb_emote($content);
|
||||
|
||||
// Restore links in the post before further parsing
|
||||
$post = defaultLinkTo($post, self::URI);
|
||||
|
||||
// Restore links in the content before adding to the item
|
||||
$content = defaultLinkTo($content, self::URI);
|
||||
|
||||
$content = $this->remove_tracking_codes($content);
|
||||
|
||||
// Retrieve date of the post
|
||||
$date = $post->find('abbr')[0];
|
||||
|
||||
//Retrieve date of the post
|
||||
$date = $post->find("abbr")[0];
|
||||
if(isset($date) && $date->hasAttribute('data-utime')) {
|
||||
$date = $date->getAttribute('data-utime');
|
||||
} else {
|
||||
$date = 0;
|
||||
}
|
||||
|
||||
//Build title from username and content
|
||||
$title = $author;
|
||||
if(strlen($title) > 24)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 24), "\n")) . '...';
|
||||
$title = $title . ' | ' . strip_tags($content);
|
||||
// Build title from content
|
||||
$title = strip_tags($post->find('.userContent', 0)->innertext);
|
||||
if(strlen($title) > 64)
|
||||
$title = substr($title, 0, strpos(wordwrap($title, 64), "\n")) . '...';
|
||||
|
||||
$uri = self::URI . $post->find('abbr')[0]->parent()->getAttribute('href');
|
||||
$uri = $post->find('abbr')[0]->parent()->getAttribute('href');
|
||||
|
||||
if (false !== strpos($uri, '?')) {
|
||||
$uri = substr($uri, 0, strpos($uri, '?'));
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item['uri'] = htmlspecialchars_decode($uri);
|
||||
$item['content'] = htmlspecialchars_decode($content);
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = htmlspecialchars_decode($uri, ENT_QUOTES);
|
||||
$item['content'] = htmlspecialchars_decode($content, ENT_QUOTES);
|
||||
$item['title'] = htmlspecialchars_decode($title, ENT_QUOTES);
|
||||
$item['author'] = htmlspecialchars_decode($author, ENT_QUOTES);
|
||||
$item['timestamp'] = $date;
|
||||
|
||||
if(strpos($item['content'], '<img') === false) {
|
||||
$item['enclosures'] = array($profilePic);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion (User)
|
||||
|
||||
public function getName(){
|
||||
if(!empty($this->authorName)) {
|
||||
return isset($this->extraInfos['name']) ? $this->extraInfos['name'] : $this->authorName
|
||||
. ' - Facebook Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class FeedExpanderExampleBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const NAME = 'FeedExpander Example';
|
||||
const URI = '#';
|
||||
const URI = 'http://github.com/RSS-Bridge/rss-bridge/';
|
||||
const DESCRIPTION = 'Example bridge to test FeedExpander';
|
||||
|
||||
const PARAMETERS = array(
|
||||
@@ -11,7 +11,6 @@ class FeedExpanderExampleBridge extends FeedExpander {
|
||||
'version' => array(
|
||||
'name' => 'Version',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'title' => 'Select your feed format/version',
|
||||
'defaultValue' => 'RSS 2.0',
|
||||
'values' => array(
|
||||
|
@@ -7,18 +7,27 @@ class FierPandaBridge extends BridgeAbstract {
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns latest articles from Fier Panda.';
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'wp-content/themes/fier-panda/img/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request Fier Panda.');
|
||||
|
||||
foreach($html->find('div.container-content article') as $element) {
|
||||
defaultLinkTo($html, static::URI);
|
||||
|
||||
foreach($html->find('article') as $article) {
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . $element->find('a', 0)->href;
|
||||
$item['title'] = trim($element->find('h1 a', 0)->innertext);
|
||||
// Remove the link at the end of the article
|
||||
$element->find('p a', 0)->outertext = '';
|
||||
$item['content'] = $element->find('p', 0)->innertext;
|
||||
|
||||
$item['uri'] = $article->find('a', 0)->href;
|
||||
$item['title'] = $article->find('a', 0)->title;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -6,6 +6,7 @@ class FilterBridge extends FeedExpander {
|
||||
const NAME = 'Filter';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Filters a feed of your choice';
|
||||
const URI = 'https://github.com/rss-bridge/rss-bridge';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'url' => array(
|
||||
@@ -26,11 +27,34 @@ class FilterBridge extends FeedExpander {
|
||||
),
|
||||
'defaultValue' => 'permit',
|
||||
),
|
||||
'title_from_content' => array(
|
||||
'name' => 'Generate title from content',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
)
|
||||
));
|
||||
|
||||
protected function parseItem($newItem){
|
||||
$item = parent::parseItem($newItem);
|
||||
|
||||
if($this->getInput('title_from_content') && array_key_exists('content', $item)) {
|
||||
|
||||
$content = str_get_html($item['content']);
|
||||
|
||||
$pos = strpos($item['content'], ' ', 50);
|
||||
|
||||
$item['title'] = substr(
|
||||
$content->plaintext,
|
||||
0,
|
||||
$pos
|
||||
);
|
||||
|
||||
if(strlen($content->plaintext) >= $pos) {
|
||||
$item['title'] .= '...';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
switch(true) {
|
||||
case $this->getFilterType() === 'permit':
|
||||
if (preg_match($this->getFilter(), $item['title'])) {
|
||||
@@ -70,7 +94,7 @@ class FilterBridge extends FeedExpander {
|
||||
}
|
||||
try{
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
} catch (HttpException $e) {
|
||||
} catch (Exception $e) {
|
||||
$this->collectExpandableDatas($this->getURI());
|
||||
}
|
||||
}
|
||||
|
82
bridges/FindACrewBridge.php
Normal file
82
bridges/FindACrewBridge.php
Normal file
@@ -0,0 +1,82 @@
|
||||
<?php
|
||||
class FindACrewBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'couraudt';
|
||||
const NAME = 'Find A Crew Bridge';
|
||||
const URI = 'https://www.findacrew.net';
|
||||
const DESCRIPTION = 'Returns the newest sailing offers.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'type' => array(
|
||||
'name' => 'Type of search',
|
||||
'title' => 'Choose between finding a boat or a crew',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Find a boat' => 'boat',
|
||||
'Find a crew' => 'crew'
|
||||
)
|
||||
),
|
||||
'long' => array(
|
||||
'name' => 'Longitude of the searched location',
|
||||
'title' => 'Center the search at that longitude (e.g: -42.02)'
|
||||
),
|
||||
'lat' => array(
|
||||
'name' => 'Latitude of the searched location',
|
||||
'title' => 'Center the search at that latitude (e.g: 12.42)'
|
||||
),
|
||||
'distance' => array(
|
||||
'name' => 'Limit boundary of search in KM',
|
||||
'title' => 'Boundary of the search in kilometers when using longitude and latitude'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
|
||||
if ($this->getInput('type') == 'boat') {
|
||||
$data = array('SrhLstBtAction' => 'Create');
|
||||
} else {
|
||||
$data = array('SrhLstCwAction' => 'Create');
|
||||
}
|
||||
|
||||
if ($this->getInput('long') && $this->getInput('lat')) {
|
||||
$data['real_LocSrh_Lng'] = $this->getInput('long');
|
||||
$data['real_LocSrh_Lat'] = $this->getInput('lat');
|
||||
if ($this->getInput('distance')) {
|
||||
$data['LocDis'] = (int)$this->getInput('distance') * 1000;
|
||||
}
|
||||
}
|
||||
|
||||
$header = array(
|
||||
'Content-Type: application/x-www-form-urlencoded'
|
||||
);
|
||||
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => http_build_query($data) . "\n"
|
||||
);
|
||||
|
||||
$html = getSimpleHTMLDOM($url, $header, $opts) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('.css_SrhRst');
|
||||
foreach ($annonces as $annonce) {
|
||||
$item = array();
|
||||
|
||||
$img = parent::getURI() . $annonce->find('.lst-pic img', 0)->getAttribute('src');
|
||||
$item['title'] = $annonce->find('.lst-tags span', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $annonce->find('.lst-ctrls a', 0)->href;
|
||||
$content = $annonce->find('.lst-dtl', 0)->innertext;
|
||||
$item['content'] = "<img src='$img' /><br>$content";
|
||||
$item['enclosures'] = array($img);
|
||||
$item['categories'] = array($annonce->find('.css_AccLocCur', 0)->plaintext);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
// Those params must be in the URL
|
||||
$uri .= '/en/' . $this->getInput('type') . '/search?srhtyp=srhrst&mdl=2';
|
||||
return $uri;
|
||||
}
|
||||
}
|
@@ -30,30 +30,76 @@ class FlickrBridge extends BridgeAbstract {
|
||||
'title' => 'Insert username (as shown in the address bar)',
|
||||
'exampleValue' => 'flickr'
|
||||
)
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
|
||||
switch($this->queriedContext) {
|
||||
|
||||
case 'Explore':
|
||||
$key = 'photos';
|
||||
$filter = 'photo-lite-models';
|
||||
$html = getSimpleHTMLDOM(self::URI . 'explore')
|
||||
or returnServerError('Could not request Flickr.');
|
||||
break;
|
||||
|
||||
case 'By keyword':
|
||||
$key = 'photos';
|
||||
$filter = 'photo-lite-models';
|
||||
$html = getSimpleHTMLDOM(self::URI . 'search/?q=' . urlencode($this->getInput('q')) . '&s=rec')
|
||||
or returnServerError('No results for this query.');
|
||||
break;
|
||||
|
||||
case 'By username':
|
||||
$key = 'photoPageList';
|
||||
$filter = 'photo-models';
|
||||
$html = getSimpleHTMLDOM(self::URI . 'photos/' . urlencode($this->getInput('u')))
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
break;
|
||||
|
||||
default:
|
||||
returnClientError('Invalid context: ' . $this->queriedContext);
|
||||
|
||||
}
|
||||
|
||||
$model_json = $this->extractJsonModel($html);
|
||||
$photo_models = $this->getPhotoModels($model_json, $filter);
|
||||
|
||||
foreach($photo_models as $model) {
|
||||
|
||||
$item = array();
|
||||
|
||||
/* Author name depends on scope. On a keyword search the
|
||||
* author is part of the picture data. On a username search
|
||||
* the author is part of the owner data.
|
||||
*/
|
||||
if(array_key_exists('username', $model)) {
|
||||
$item['author'] = $model['username'];
|
||||
} elseif (array_key_exists('owner', reset($model_json)[0])) {
|
||||
$item['author'] = reset($model_json)[0]['owner']['username'];
|
||||
}
|
||||
|
||||
$item['title'] = (array_key_exists('title', $model) ? $model['title'] : 'Untitled');
|
||||
$item['uri'] = self::URI . 'photo.gne?id=' . $model['id'];
|
||||
|
||||
$description = (array_key_exists('description', $model) ? $model['description'] : '');
|
||||
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $this->extractContentImage($model)
|
||||
. '" style="max-width: 640px; max-height: 480px;"/></a><br><p>'
|
||||
. $description
|
||||
. '</p>';
|
||||
|
||||
$item['enclosures'] = $this->extractEnclosures($model);
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function extractJsonModel($html) {
|
||||
|
||||
// Find SCRIPT containing JSON data
|
||||
$model = $html->find('.modelExport', 0);
|
||||
$model_text = $model->innertext;
|
||||
@@ -62,59 +108,78 @@ class FlickrBridge extends BridgeAbstract {
|
||||
$start = strpos($model_text, 'modelExport:') + strlen('modelExport:');
|
||||
$end = strpos($model_text, 'auth:') - strlen('auth:');
|
||||
|
||||
// Dissect JSON data and remove trailing comma
|
||||
// Extract JSON data, remove trailing comma
|
||||
$model_text = trim(substr($model_text, $start, $end - $start));
|
||||
$model_text = substr($model_text, 0, strlen($model_text) - 1);
|
||||
|
||||
$model_json = json_decode($model_text, true);
|
||||
return json_decode($model_text, true);
|
||||
|
||||
foreach($html->find('.photo-list-photo-view') as $element) {
|
||||
// Get the styles
|
||||
$style = explode(';', $element->style);
|
||||
}
|
||||
|
||||
// Get the background-image style
|
||||
$backgroundImage = explode(':', end($style));
|
||||
private function getPhotoModels($json, $filter) {
|
||||
|
||||
// URI type : url(//cX.staticflickr.com/X/XXXXX/XXXXXXXXX.jpg)
|
||||
$imageURI = trim(str_replace(['url(', ')'], '', end($backgroundImage)));
|
||||
// The JSON model contains a "legend" array, where each element contains
|
||||
// the path to an element in the "main" object
|
||||
$photo_models = array();
|
||||
|
||||
// Get the image ID
|
||||
$imageURIs = explode('_', basename($imageURI));
|
||||
$imageID = reset($imageURIs);
|
||||
foreach($json['legend'] as $legend) {
|
||||
|
||||
// Use JSON data to build items
|
||||
foreach(reset($model_json)[0][$key]['_data'] as $element) {
|
||||
if($element['id'] === $imageID) {
|
||||
$item = array();
|
||||
$photo_model = $json['main'];
|
||||
|
||||
/* Author name depends on scope. On a keyword search the
|
||||
* author is part of the picture data. On a username search
|
||||
* the author is part of the owner data.
|
||||
*/
|
||||
if(array_key_exists('username', $element)) {
|
||||
$item['author'] = $element['username'];
|
||||
} elseif (array_key_exists('owner', reset($model_json)[0])) {
|
||||
$item['author'] = reset($model_json)[0]['owner']['username'];
|
||||
}
|
||||
|
||||
$item['title'] = (array_key_exists('title', $element) ? $element['title'] : 'Untitled');
|
||||
$item['uri'] = self::URI . 'photo.gne?id=' . $imageID;
|
||||
|
||||
$description = (array_key_exists('description', $element) ? $element['description'] : '');
|
||||
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $imageURI
|
||||
. '" /></a><br><p>'
|
||||
. $description
|
||||
. '</p>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
break;
|
||||
}
|
||||
foreach($legend as $element) { // Traverse tree
|
||||
$photo_model = $photo_model[$element];
|
||||
}
|
||||
|
||||
// We are only interested in content
|
||||
if($photo_model['_flickrModelRegistry'] === $filter) {
|
||||
$photo_models[] = $photo_model;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $photo_models;
|
||||
|
||||
}
|
||||
|
||||
private function extractEnclosures($model) {
|
||||
|
||||
$areas = array();
|
||||
|
||||
foreach($model['sizes'] as $size) {
|
||||
$areas[$size['width'] * $size['height']] = $size['url'];
|
||||
}
|
||||
|
||||
return array($this->fixURL(max($areas)));
|
||||
|
||||
}
|
||||
|
||||
private function extractContentImage($model) {
|
||||
|
||||
$areas = array();
|
||||
$limit = 320 * 240;
|
||||
|
||||
foreach($model['sizes'] as $size) {
|
||||
|
||||
$image_area = $size['width'] * $size['height'];
|
||||
|
||||
if($image_area >= $limit) {
|
||||
$areas[$image_area] = $size['url'];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this->fixURL(min($areas));
|
||||
|
||||
}
|
||||
|
||||
private function fixURL($url) {
|
||||
|
||||
// For some reason the image URLs don't include the protocol (https)
|
||||
if(strpos($url, '//') === 0) {
|
||||
$url = 'https:' . $url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -15,47 +15,47 @@ class FootitoBridge extends BridgeAbstract {
|
||||
|
||||
$content = trim($element->innertext);
|
||||
$content = str_replace(
|
||||
"<img",
|
||||
'<img',
|
||||
"<img style='float : left;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"logo\"",
|
||||
'class="logo"',
|
||||
"style='float : left;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"contenu\"",
|
||||
'class="contenu"',
|
||||
"style='margin-left : 60px;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"responsive-comment\"",
|
||||
'class="responsive-comment"',
|
||||
"style='border-top : 1px #DDD solid; background-color : white; padding : 10px;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"jaime\"",
|
||||
'class="jaime"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"auteur-event responsive\"",
|
||||
'class="auteur-event responsive"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"report-abuse-button\"",
|
||||
'class="report-abuse-button"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"reaction clearfix\"",
|
||||
'class="reaction clearfix"',
|
||||
"style='margin : 10px 0px; padding : 5px; border-bottom : 1px #DDD solid;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
"class=\"infos\"",
|
||||
'class="infos"',
|
||||
"style='font-size : 0.7em;'",
|
||||
$content );
|
||||
|
||||
|
40
bridges/ForGifsBridge.php
Normal file
40
bridges/ForGifsBridge.php
Normal file
@@ -0,0 +1,40 @@
|
||||
<?php
|
||||
class ForGifsBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const NAME = 'forgifs Bridge';
|
||||
const URI = 'https://forgifs.com';
|
||||
const DESCRIPTION = 'Returns the forgifs feed with actual gifs instead of images';
|
||||
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas('https://forgifs.com/gallery/srss/7');
|
||||
}
|
||||
|
||||
protected function parseItem($feedItem) {
|
||||
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
$content = str_get_html($item['content']);
|
||||
$img = $content->find('img', 0);
|
||||
$poster = $img->src;
|
||||
|
||||
// The actual gif is the same path but its id must be decremented by one.
|
||||
// Example:
|
||||
// http://forgifs.com/gallery/d/279419-2/Reporter-videobombed-shoulder-checks.gif
|
||||
// http://forgifs.com/gallery/d/279418-2/Reporter-videobombed-shoulder-checks.gif
|
||||
// Notice how this changes ----------^
|
||||
// Now let's extract that number and do some math
|
||||
// Notice: Technically we could also load the content page but that would
|
||||
// require unnecessary traffic. As long as it works...
|
||||
$num = substr($img->src, 29, 6);
|
||||
$num -= 1;
|
||||
$img->src = substr_replace($img->src, $num, 29, strlen($num));
|
||||
$img->width = 'auto';
|
||||
$img->height = 'auto';
|
||||
|
||||
$item['content'] = $content;
|
||||
|
||||
return $item;
|
||||
|
||||
}
|
||||
}
|
@@ -30,7 +30,7 @@ class FourchanBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError("Could not request 4chan, thread not found");
|
||||
or returnServerError('Could not request 4chan, thread not found');
|
||||
|
||||
foreach($html->find('div.postContainer') as $element) {
|
||||
$item = array();
|
||||
@@ -69,7 +69,7 @@ class FourchanBridge extends BridgeAbstract {
|
||||
. '" src="'
|
||||
. $item['imageThumb']
|
||||
. '" /></a><br>'
|
||||
.$item['content'];
|
||||
. $item['content'];
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -3,7 +3,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Futura-Sciences Bridge';
|
||||
const URI = 'http://www.futura-sciences.com/';
|
||||
const URI = 'https://www.futura-sciences.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
@@ -90,42 +90,11 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
or returnServerError('Could not request Futura-Sciences: ' . $item['uri']);
|
||||
$item['content'] = $this->extractArticleContent($article);
|
||||
$author = $this->extractAuthor($article);
|
||||
$item['author'] = empty($author) ? $item['author'] : $author;
|
||||
if (!empty($author))
|
||||
$item['author'] = $author;
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
} return $string;
|
||||
}
|
||||
|
||||
private function stripRecursiveHTMLSection($string, $tag_name, $tag_start){
|
||||
$open_tag = '<' . $tag_name;
|
||||
$close_tag = '</' . $tag_name . '>';
|
||||
$close_tag_length = strlen($close_tag);
|
||||
if(strpos($tag_start, $open_tag) === 0) {
|
||||
while(strpos($string, $tag_start) !== false) {
|
||||
$max_recursion = 100;
|
||||
$section_to_remove = null;
|
||||
$section_start = strpos($string, $tag_start);
|
||||
$search_offset = $section_start;
|
||||
do {
|
||||
$max_recursion--;
|
||||
$section_end = strpos($string, $close_tag, $search_offset);
|
||||
$search_offset = $section_end + $close_tag_length;
|
||||
$section_to_remove = substr($string, $section_start, $section_end - $section_start + $close_tag_length);
|
||||
$open_tag_count = substr_count($section_to_remove, $open_tag);
|
||||
$close_tag_count = substr_count($section_to_remove, $close_tag);
|
||||
} while ($open_tag_count > $close_tag_count && $max_recursion > 0);
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function extractArticleContent($article){
|
||||
$contents = $article->find('section.article-text-classic', 0)->innertext;
|
||||
$headline = trim($article->find('p.description', 0)->plaintext);
|
||||
@@ -137,6 +106,7 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
'<div class="sharebar2',
|
||||
'<div class="diaporamafullscreen"',
|
||||
'<div class="module social-button',
|
||||
'<div class="module social-share',
|
||||
'<div style="margin-bottom:10px;" class="noprint"',
|
||||
'<div class="ficheprevnext',
|
||||
'<div class="bar noprint',
|
||||
@@ -148,16 +118,17 @@ class FuturaSciencesBridge extends FeedExpander {
|
||||
'<div id="forumcomments',
|
||||
'<div ng-if="active"'
|
||||
) as $div_start) {
|
||||
$contents = $this->stripRecursiveHTMLSection($contents, 'div', $div_start);
|
||||
$contents = stripRecursiveHTMLSection($contents, 'div', $div_start);
|
||||
}
|
||||
|
||||
$contents = $this->stripWithDelimiters($contents, '<hr ', '/>');
|
||||
$contents = $this->stripWithDelimiters($contents, '<p class="content-date', '</p>');
|
||||
$contents = $this->stripWithDelimiters($contents, '<h1 class="content-title', '</h1>');
|
||||
$contents = $this->stripWithDelimiters($contents, 'fs:definition="', '"');
|
||||
$contents = $this->stripWithDelimiters($contents, 'fs:xt:clicktype="', '"');
|
||||
$contents = $this->stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
|
||||
$contents = $this->stripWithDelimiters($contents, '<script ', '</script>');
|
||||
$contents = stripWithDelimiters($contents, '<hr ', '/>');
|
||||
$contents = stripWithDelimiters($contents, '<p class="content-date', '</p>');
|
||||
$contents = stripWithDelimiters($contents, '<h1 class="content-title', '</h1>');
|
||||
$contents = stripWithDelimiters($contents, 'fs:definition="', '"');
|
||||
$contents = stripWithDelimiters($contents, 'fs:xt:clicktype="', '"');
|
||||
$contents = stripWithDelimiters($contents, 'fs:xt:clickname="', '"');
|
||||
$contents = StripWithDelimiters($contents, '<section class="module-toretain module-propal-nl', '</section>');
|
||||
$contents = stripWithDelimiters($contents, '<script ', '</script>');
|
||||
|
||||
return $headline . trim($contents);
|
||||
}
|
||||
|
@@ -10,7 +10,6 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
'type' => array(
|
||||
'name' => 'Type',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'News' => 'N',
|
||||
'Reviews' => 'R',
|
||||
@@ -20,50 +19,58 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
private function extractFromDelimiters($string, $start, $end){
|
||||
if(strpos($string, $start) !== false) {
|
||||
$section_retrieved = substr($string, strpos($string, $start) + strlen($start));
|
||||
$section_retrieved = substr($section_retrieved, 0, strpos($section_retrieved, $end));
|
||||
return $section_retrieved;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function stripWithDelimiters($string, $start, $end){
|
||||
while(strpos($string, $start) !== false) {
|
||||
$section_to_remove = substr($string, strpos($string, $start));
|
||||
$section_to_remove = substr($section_to_remove, 0, strpos($section_to_remove, $end) + strlen($end));
|
||||
$string = str_replace($section_to_remove, '', $string);
|
||||
}
|
||||
|
||||
return $string;
|
||||
}
|
||||
|
||||
private function buildItem($uri, $title, $author, $timestamp, $content){
|
||||
private function buildItem($uri, $title, $author, $timestamp, $thumbnail, $content){
|
||||
$item = array();
|
||||
$item['uri'] = $uri;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = $author;
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content;
|
||||
if (!empty($thumbnail)) {
|
||||
$item['enclosures'] = array($thumbnail);
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function cleanupPostContent($content, $site_url){
|
||||
$content = str_replace(':arrow:', '➤', $content);
|
||||
$content = str_replace('href="attachments/', 'href="'.$site_url.'attachments/', $content);
|
||||
$content = $this->stripWithDelimiters($content, '<script', '</script>');
|
||||
$content = str_replace('href="attachments/', 'href="' . $site_url . 'attachments/', $content);
|
||||
$content = stripWithDelimiters($content, '<script', '</script>');
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function findItemDate($item){
|
||||
$time = 0;
|
||||
$dateField = $item->find('abbr.DateTime', 0);
|
||||
if (is_object($dateField)) {
|
||||
$time = intval(
|
||||
extractFromDelimiters(
|
||||
$dateField->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$dateField = $item->find('span.DateTime', 0);
|
||||
$time = DateTime::createFromFormat(
|
||||
'M j, Y \a\t g:i A',
|
||||
extractFromDelimiters(
|
||||
$dateField->outertext,
|
||||
'title="',
|
||||
'"'
|
||||
)
|
||||
)->getTimestamp();
|
||||
}
|
||||
return $time;
|
||||
}
|
||||
|
||||
private function fetchPostContent($uri, $site_url){
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
if(!$html) {
|
||||
return 'Could not request GBAtemp ' . $uri;
|
||||
return 'Could not request GBAtemp: ' . $uri;
|
||||
}
|
||||
|
||||
$content = $html->find('div.messageContent', 0)->innertext;
|
||||
$content = $html->find('div.messageContent, blockquote.baseHtml', 0)->innertext;
|
||||
return $this->cleanupPostContent($content, $site_url);
|
||||
}
|
||||
|
||||
@@ -76,70 +83,56 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
case 'N':
|
||||
foreach($html->find('li[class=news_item full]') as $newsItem) {
|
||||
$url = self::URI . $newsItem->find('a', 0)->href;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$newsItem->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$img = $this->getURI() . $newsItem->find('img', 0)->src . '#.image';
|
||||
$time = $this->findItemDate($newsItem);
|
||||
$author = $newsItem->find('a.username', 0)->plaintext;
|
||||
$title = $newsItem->find('a', 1)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
|
||||
unset($newsItem); // Some items are heavy, freeing the item proactively helps saving memory
|
||||
}
|
||||
break;
|
||||
case 'R':
|
||||
foreach($html->find('li.portal_review') as $reviewItem) {
|
||||
$url = self::URI . $reviewItem->find('a', 0)->href;
|
||||
$img = $this->getURI() . extractFromDelimiters($reviewItem->find('a', 0)->style, 'image:url(', ')');
|
||||
$title = $reviewItem->find('span.review_title', 0)->plaintext;
|
||||
$content = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request GBAtemp: ' . $uri);
|
||||
$author = $content->find('a.username', 0)->plaintext;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$content->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$time = $this->findItemDate($content);
|
||||
$intro = '<p><b>' . ($content->find('div#review_intro', 0)->plaintext) . '</b></p>';
|
||||
$review = $content->find('div#review_main', 0)->innertext;
|
||||
$subheader = '<p><b>' . $content->find('div.review_subheader', 0)->plaintext . '</b></p>';
|
||||
$procons = $content->find('table.review_procons', 0)->outertext;
|
||||
$scores = $content->find('table.reviewscores', 0)->outertext;
|
||||
$content = $this->cleanupPostContent($intro . $review . $subheader . $procons . $scores, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
|
||||
unset($reviewItem); // Free up memory
|
||||
}
|
||||
break;
|
||||
case 'T':
|
||||
foreach($html->find('li.portal-tutorial') as $tutorialItem) {
|
||||
$url = self::URI . $tutorialItem->find('a', 0)->href;
|
||||
$title = $tutorialItem->find('a', 0)->plaintext;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$tutorialItem->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$time = $this->findItemDate($tutorialItem);
|
||||
$author = $tutorialItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, null, $content);
|
||||
unset($tutorialItem); // Free up memory
|
||||
}
|
||||
break;
|
||||
case 'F':
|
||||
foreach($html->find('li.rc_item') as $postItem) {
|
||||
$url = self::URI . $postItem->find('a', 1)->href;
|
||||
$title = $postItem->find('a', 1)->plaintext;
|
||||
$time = intval(
|
||||
$this->extractFromDelimiters(
|
||||
$postItem->find('abbr.DateTime', 0)->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
$time = $this->findItemDate($postItem);
|
||||
$author = $postItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $content);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, null, $content);
|
||||
unset($postItem); // Free up memory
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
65
bridges/GOGBridge.php
Normal file
65
bridges/GOGBridge.php
Normal file
@@ -0,0 +1,65 @@
|
||||
<?php
|
||||
class GOGBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'GOGBridge';
|
||||
const MAINTAINER = 'teromene';
|
||||
const URI = 'https://gog.com';
|
||||
const DESCRIPTION = 'Returns the latest releases from GOG.com';
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$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;
|
||||
foreach($decodedValues->products as $game) {
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $game->developer . ' / ' . $game->publisher;
|
||||
$item['title'] = $game->title;
|
||||
$item['id'] = $game->id;
|
||||
$item['uri'] = self::URI . $game->url;
|
||||
$item['content'] = $this->buildGameContentPage($game);
|
||||
$item['timestamp'] = $game->globalReleaseDate;
|
||||
|
||||
foreach($game->gallery as $image) {
|
||||
$item['enclosures'][] = $image . '.jpg';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
$limit += 1;
|
||||
|
||||
if($limit == 10) break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function buildGameContentPage($game) {
|
||||
|
||||
$gameDescriptionText = getContents('https://api.gog.com/products/' . $game->id . '?expand=description')
|
||||
or returnServerError('Unable to get game description from GOG !');
|
||||
|
||||
$gameDescriptionValue = json_decode($gameDescriptionText);
|
||||
|
||||
$content = 'Genres: ';
|
||||
$content .= implode(', ', $game->genres);
|
||||
|
||||
$content .= '<br />Supported Platforms: ';
|
||||
if($game->worksOn->Windows) {
|
||||
$content .= 'Windows ';
|
||||
}
|
||||
if($game->worksOn->Mac) {
|
||||
$content .= 'Mac ';
|
||||
}
|
||||
if($game->worksOn->Linux) {
|
||||
$content .= 'Linux ';
|
||||
}
|
||||
|
||||
$content .= '<br />' . $gameDescriptionValue->description->full;
|
||||
|
||||
return $content;
|
||||
|
||||
}
|
||||
}
|
123
bridges/GQMagazineBridge.php
Normal file
123
bridges/GQMagazineBridge.php
Normal file
@@ -0,0 +1,123 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* An extension of the previous SexactuBridge to cover the whole GQMagazine.
|
||||
* This one taks a page (as an example sexe/news or journaliste/maia-mazaurette) which is to be configured,
|
||||
* reads all the articles visible on that page, and make a stream out of it.
|
||||
* @author nicolas-delsaux
|
||||
*
|
||||
*/
|
||||
class GQMagazineBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Riduidel';
|
||||
|
||||
const NAME = 'GQMagazine';
|
||||
|
||||
// URI is no more valid, since we can address the whole gq galaxy
|
||||
const URI = 'https://www.gqmagazine.fr';
|
||||
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'GQMagazine section extractor bridge. This bridge allows you get only a specific section.';
|
||||
|
||||
const DEFAULT_DOMAIN = 'www.gqmagazine.fr';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'domain' => array(
|
||||
'name' => 'Domain to use',
|
||||
'required' => true,
|
||||
'defaultValue' => self::DEFAULT_DOMAIN
|
||||
),
|
||||
'page' => array(
|
||||
'name' => 'Initial page to load',
|
||||
'required' => true,
|
||||
'exampleValue' => 'sexe/news'
|
||||
),
|
||||
));
|
||||
|
||||
const REPLACED_ATTRIBUTES = array(
|
||||
'href' => 'href',
|
||||
'src' => 'src',
|
||||
'data-original' => 'src'
|
||||
);
|
||||
|
||||
private function getDomain() {
|
||||
$domain = $this->getInput('domain');
|
||||
if (empty($domain))
|
||||
$domain = self::DEFAULT_DOMAIN;
|
||||
if (strpos($domain, '://') === false)
|
||||
$domain = 'https://' . $domain;
|
||||
return $domain;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return $this->getDomain() . '/' . $this->getInput('page');
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI()) or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
// 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) {
|
||||
$uri = $link->href;
|
||||
$title = $link->find('h2', 0);
|
||||
$date = $link->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;
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the full article and returns the contents
|
||||
* @param $uri The article URI
|
||||
* @return The article content
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces all relative URIs with absolute ones
|
||||
* @param $element A simplehtmldom element
|
||||
* @return The $element->innertext with all URIs replaced
|
||||
*/
|
||||
private function replaceUriInHtmlElement($element){
|
||||
$returned = $element->innertext;
|
||||
foreach (self::REPLACED_ATTRIBUTES as $initial => $final) {
|
||||
$returned = str_replace($initial . '="/', $final . '="' . self::URI . '/', $returned);
|
||||
}
|
||||
return $returned;
|
||||
}
|
||||
}
|
163
bridges/GitHubGistBridge.php
Normal file
163
bridges/GitHubGistBridge.php
Normal file
@@ -0,0 +1,163 @@
|
||||
<?php
|
||||
|
||||
class GitHubGistBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'GitHubGist comment bridge';
|
||||
const URI = 'https://gist.github.com';
|
||||
const DESCRIPTION = 'Generates feeds for Gist comments';
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'id' => array(
|
||||
'name' => 'Gist',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert Gist ID or URI',
|
||||
'exampleValue' => '2646763, https://gist.github.com/2646763'
|
||||
)
|
||||
));
|
||||
|
||||
private $filename;
|
||||
|
||||
public function getURI() {
|
||||
|
||||
$id = $this->getInput('id') ?: '';
|
||||
|
||||
$urlpath = parse_url($id, PHP_URL_PATH);
|
||||
|
||||
if($urlpath) {
|
||||
|
||||
$components = explode('/', $urlpath);
|
||||
$id = end($components);
|
||||
|
||||
}
|
||||
|
||||
return static::URI . '/' . $id;
|
||||
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->filename ? $this->filename . ' - ' . static::NAME : static::NAME;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI(),
|
||||
null,
|
||||
null,
|
||||
true,
|
||||
true,
|
||||
DEFAULT_TARGET_CHARSET,
|
||||
false, // Do NOT remove line breaks
|
||||
DEFAULT_BR_TEXT,
|
||||
DEFAULT_SPAN_TEXT)
|
||||
or returnServerError('Could not request ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$fileinfo = $html->find('[class="file-info"]', 0)
|
||||
or returnServerError('Could not find file info!');
|
||||
|
||||
$this->filename = $fileinfo->plaintext;
|
||||
|
||||
$comments = $html->find('div[class="timeline-comment-wrapper"]');
|
||||
|
||||
if(is_null($comments)) { // no comments yet
|
||||
return;
|
||||
}
|
||||
|
||||
foreach($comments as $comment) {
|
||||
|
||||
$uri = $comment->find('a[href*=#gistcomment]', 0)
|
||||
or returnServerError('Could not find comment anchor!');
|
||||
|
||||
$title = $comment->find('div[class="unminimized-comment"] h3[class="timeline-comment-header-text"]', 0)
|
||||
or returnServerError('Could not find comment header text!');
|
||||
|
||||
$datetime = $comment->find('[datetime]', 0)
|
||||
or returnServerError('Could not find comment datetime!');
|
||||
|
||||
$author = $comment->find('a.author', 0)
|
||||
or returnServerError('Could not find author name!');
|
||||
|
||||
$message = $comment->find('[class="comment-body"]', 0)
|
||||
or returnServerError('Could not find comment body!');
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $uri->href;
|
||||
$item['title'] = str_replace('commented', 'commented on', $title->plaintext);
|
||||
$item['timestamp'] = strtotime($datetime->datetime);
|
||||
$item['author'] = '<a href="' . $author->href . '">' . $author->plaintext . '</a>';
|
||||
$item['content'] = $this->fixContent($message);
|
||||
// $item['enclosures'] = array();
|
||||
// $item['categories'] = array();
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/** Removes all unnecessary tags and adds formatting */
|
||||
private function fixContent($content){
|
||||
|
||||
// Restore code (inside <pre />) highlighting
|
||||
foreach($content->find('pre') as $pre) {
|
||||
|
||||
$pre->style = <<<EOD
|
||||
padding: 16px;
|
||||
overflow: auto;
|
||||
font-size: 85%;
|
||||
line-height: 1.45;
|
||||
background-color: #f6f8fa;
|
||||
border-radius: 3px;
|
||||
word-wrap: normal;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 16px;
|
||||
EOD;
|
||||
|
||||
$code = $pre->find('code', 0);
|
||||
|
||||
if($code) {
|
||||
|
||||
$code->style = <<<EOD
|
||||
white-space: pre;
|
||||
word-break: normal;
|
||||
EOD;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// find <code /> not inside <pre /> (`inline-code`)
|
||||
foreach($content->find('code') as $code) {
|
||||
|
||||
if($code->parent()->tag === 'pre') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$code->style = <<<EOD
|
||||
background-color: rgba(27,31,35,0.05);
|
||||
padding: 0.2em 0.4em;
|
||||
border-radius: 3px;
|
||||
EOD;
|
||||
|
||||
}
|
||||
|
||||
// restore text spacing
|
||||
foreach($content->find('p') as $p) {
|
||||
$p->style = 'margin-bottom: 16px;';
|
||||
}
|
||||
|
||||
// Remove unnecessary tags
|
||||
$content = strip_tags(
|
||||
$content->innertext,
|
||||
'<p><a><img><ol><ul><li><table><tr><th><td><string><pre><code><br><hr><h>'
|
||||
);
|
||||
|
||||
return $content;
|
||||
|
||||
}
|
||||
}
|
@@ -28,7 +28,7 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
'i' => array(
|
||||
'name' => 'Issue number',
|
||||
'type' => 'number',
|
||||
'required' => 'true'
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -37,10 +37,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
$name = $this->getInput('u') . '/' . $this->getInput('p');
|
||||
switch($this->queriedContext) {
|
||||
case 'Project Issues':
|
||||
$prefix = static::NAME . 's for ';
|
||||
if($this->getInput('c')) {
|
||||
$prefix = static::NAME . 's comments for ';
|
||||
} else {
|
||||
$prefix = static::NAME . 's for ';
|
||||
}
|
||||
$name = $prefix . $name;
|
||||
break;
|
||||
@@ -53,8 +52,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u')) && !is_null($this->getInput('p'))) {
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues';
|
||||
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
|
||||
$uri = static::URI . $this->getInput('u') . '/'
|
||||
. $this->getInput('p') . '/issues';
|
||||
if($this->queriedContext === 'Issue comments') {
|
||||
$uri .= '/' . $this->getInput('i');
|
||||
} elseif($this->getInput('c')) {
|
||||
@@ -66,54 +66,54 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
protected function extractIssueComment($issueNbr, $title, $comment){
|
||||
$class = $comment->getAttribute('class');
|
||||
$classes = explode(' ', $class);
|
||||
$event = false;
|
||||
if(in_array('discussion-item', $classes)) {
|
||||
$event = true;
|
||||
}
|
||||
|
||||
$author = 'unknown';
|
||||
if($comment->find('.author', 0)) {
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
}
|
||||
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
|
||||
protected function extractIssueEvent($issueNbr, $title, $comment){
|
||||
$comment = $comment->firstChild();
|
||||
if(!$event) {
|
||||
$comment = $comment->nextSibling();
|
||||
}
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p')
|
||||
. '/issues/' . $issueNbr . '#' . $comment->getAttribute('id');
|
||||
|
||||
if($event) {
|
||||
$title .= ' / ' . substr($class, strpos($class, 'discussion-item-') + strlen('discussion-item-'));
|
||||
if(!$comment->hasAttribute('id')) {
|
||||
$items = array();
|
||||
$timestamp = strtotime($comment->find('relative-time', 0)->getAttribute('datetime'));
|
||||
$content = $comment->innertext;
|
||||
while($comment = $comment->nextSibling()) {
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content . '<p>' . $comment->children(1)->innertext . '</p>';
|
||||
$item['uri'] = $uri . '#' . $comment->children(1)->getAttribute('id');
|
||||
$items[] = $item;
|
||||
}
|
||||
return $items;
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
$title .= ' / ' . trim($comment->plaintext);
|
||||
|
||||
$content = $title;
|
||||
if (null !== $comment->nextSibling()) {
|
||||
$content = $comment->nextSibling()->innertext;
|
||||
if ($comment->nextSibling()->nodeName() === 'span') {
|
||||
$content = $comment->nextSibling()->nextSibling()->innertext;
|
||||
}
|
||||
$content = $comment->parent()->innertext;
|
||||
} else {
|
||||
$title .= ' / ' . trim($comment->firstChild()->plaintext);
|
||||
$content = "<pre>" . $comment->find('.comment-body', 0)->innertext . "</pre>";
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['author'] = $author;
|
||||
$item['uri'] = $uri . '#' . $comment->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'));
|
||||
$item['timestamp'] = strtotime(
|
||||
$comment->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
}
|
||||
|
||||
protected function extractIssueComment($issueNbr, $title, $comment){
|
||||
$uri = static::URI . $this->getInput('u') . '/'
|
||||
. $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
|
||||
$author = $comment->find('.author', 0)->plaintext;
|
||||
|
||||
$title .= ' / ' . trim(
|
||||
$comment->find('.comment .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['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
|
||||
$item['timestamp'] = strtotime(
|
||||
$comment->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['content'] = $content;
|
||||
return $item;
|
||||
}
|
||||
@@ -121,17 +121,29 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
protected function extractIssueComments($issue){
|
||||
$items = array();
|
||||
$title = $issue->find('.gh-header-title', 0)->plaintext;
|
||||
$issueNbr = trim(substr($issue->find('.gh-header-number', 0)->plaintext, 1));
|
||||
$issueNbr = trim(
|
||||
substr($issue->find('.gh-header-number', 0)->plaintext, 1)
|
||||
);
|
||||
$comments = $issue->find('.js-discussion', 0);
|
||||
foreach($comments->children() as $comment) {
|
||||
if (!$comment->hasChildNodes()) {
|
||||
continue;
|
||||
}
|
||||
$comment = $comment->firstChild();
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
if(in_array('discussion-item', $classes)
|
||||
|| in_array('timeline-comment-wrapper', $classes)) {
|
||||
if (in_array('timeline-comment-wrapper', $classes)) {
|
||||
$item = $this->extractIssueComment($issueNbr, $title, $comment);
|
||||
if(array_keys($item) !== range(0, count($item) - 1)) {
|
||||
$item = array($item);
|
||||
$items[] = $item;
|
||||
continue;
|
||||
}
|
||||
while (in_array('discussion-item', $classes)) {
|
||||
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
|
||||
$items[] = $item;
|
||||
$comment = $comment->nextSibling();
|
||||
if (null == $comment) {
|
||||
break;
|
||||
}
|
||||
$items = array_merge($items, $item);
|
||||
$classes = explode(' ', $comment->getAttribute('class'));
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
@@ -139,7 +151,9 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for Github Issue ' . $this->getURI());
|
||||
or returnServerError(
|
||||
'No results for Github Issue ' . $this->getURI()
|
||||
);
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Issue comments':
|
||||
@@ -148,31 +162,40 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
case 'Project Issues':
|
||||
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
|
||||
$info = $issue->find('.opened-by', 0);
|
||||
$issueNbr = substr(trim($info->plaintext), 1, strpos(trim($info->plaintext), ' '));
|
||||
$issueNbr = substr(
|
||||
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
|
||||
);
|
||||
|
||||
$item = array();
|
||||
$item['content'] = '';
|
||||
|
||||
if($this->getInput('c')) {
|
||||
$uri = static::URI . $this->getInput('u') . '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
$uri = static::URI . $this->getInput('u')
|
||||
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
|
||||
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
|
||||
if($issue) {
|
||||
$this->items = array_merge($this->items, $this->extractIssueComments($issue));
|
||||
$this->items = array_merge(
|
||||
$this->items,
|
||||
$this->extractIssueComments($issue)
|
||||
);
|
||||
continue;
|
||||
}
|
||||
$item['content'] = 'Can not extract comments from ' . $uri;
|
||||
}
|
||||
|
||||
$item['author'] = $info->find('a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($info->find('relative-time', 0)->getAttribute('datetime'));
|
||||
$item['timestamp'] = strtotime(
|
||||
$info->find('relative-time', 0)->getAttribute('datetime')
|
||||
);
|
||||
$item['title'] = html_entity_decode(
|
||||
$issue->find('.js-navigation-open', 0)->plaintext,
|
||||
ENT_QUOTES,
|
||||
'UTF-8'
|
||||
);
|
||||
$comments = $issue->find('.col-5', 0)->plaintext;
|
||||
$comments = trim($issue->find('.col-5', 0)->plaintext);
|
||||
$item['content'] .= "\n" . 'Comments: ' . ($comments ? $comments : '0');
|
||||
$item['uri'] = self::URI . $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
$item['uri'] = self::URI
|
||||
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
@@ -180,7 +203,11 @@ class GithubIssueBridge extends BridgeAbstract {
|
||||
|
||||
array_walk($this->items, function(&$item){
|
||||
$item['content'] = preg_replace('/\s+/', ' ', $item['content']);
|
||||
$item['content'] = str_replace('href="/', 'href="' . static::URI, $item['content']);
|
||||
$item['content'] = str_replace(
|
||||
'href="/',
|
||||
'href="' . static::URI,
|
||||
$item['content']
|
||||
);
|
||||
$item['content'] = str_replace(
|
||||
'href="#',
|
||||
'href="' . substr($item['uri'], 0, strpos($item['uri'], '#') + 1),
|
||||
|
@@ -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;
|
||||
@@ -34,13 +34,29 @@ class GithubSearchBridge extends BridgeAbstract {
|
||||
$title = $element->find('h3', 0)->plaintext;
|
||||
$item['title'] = $title;
|
||||
|
||||
if (count($element->find('p')) == 2) {
|
||||
$content = $element->find('p', 0)->innertext;
|
||||
// Description
|
||||
if (count($element->find('p.d-inline-block')) != 0) {
|
||||
$content = $element->find('p.d-inline-block', 0)->innertext;
|
||||
} else{
|
||||
$content = '';
|
||||
$content = 'No description';
|
||||
}
|
||||
$item['content'] = $content;
|
||||
|
||||
// Tags
|
||||
$content = $content . '<br />';
|
||||
$tags = $element->find('a.topic-tag');
|
||||
$tags_array = array();
|
||||
if (count($tags) != 0) {
|
||||
$content = $content . 'Tags : ';
|
||||
foreach($tags as $tag_element) {
|
||||
$tag_link = 'https://github.com' . $tag_element->href;
|
||||
$tag_name = trim($tag_element->innertext);
|
||||
$content = $content . '<a href="' . $tag_link . '">' . $tag_name . '</a> ';
|
||||
array_push($tags_array, $tag_element->innertext);
|
||||
}
|
||||
}
|
||||
|
||||
$item['categories'] = $tags_array;
|
||||
$item['content'] = $content;
|
||||
$date = $element->find('relative-time', 0)->datetime;
|
||||
$item['timestamp'] = strtotime($date);
|
||||
|
||||
|
221
bridges/GlassdoorBridge.php
Normal file
221
bridges/GlassdoorBridge.php
Normal file
@@ -0,0 +1,221 @@
|
||||
<?php
|
||||
class GlassdoorBridge extends BridgeAbstract {
|
||||
|
||||
// Contexts
|
||||
const CONTEXT_BLOG = 'Blogs';
|
||||
const CONTEXT_REVIEW = 'Company Reviews';
|
||||
const CONTEXT_GLOBAL = 'global';
|
||||
|
||||
// Global context parameters
|
||||
const PARAM_LIMIT = 'limit';
|
||||
|
||||
// Blog context parameters
|
||||
const PARAM_BLOG_TYPE = 'blog_type';
|
||||
const PARAM_BLOG_FULL = 'full_article';
|
||||
|
||||
const BLOG_TYPE_HOME = 'Home';
|
||||
const BLOG_TYPE_COMPANIES_HIRING = 'Companies Hiring';
|
||||
const BLOG_TYPE_CAREER_ADVICE = 'Career Advice';
|
||||
const BLOG_TYPE_INTERVIEWS = 'Interviews';
|
||||
const BLOG_TYPE_GUIDE = 'Guides';
|
||||
|
||||
// Review context parameters
|
||||
const PARAM_REVIEW_COMPANY = 'company';
|
||||
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const NAME = 'Glassdoor Bridge';
|
||||
const URI = 'https://www.glassdoor.com/';
|
||||
const DESCRIPTION = 'Returns feeds for blog posts and company reviews';
|
||||
const CACHE_TIMEOUT = 86400; // 24 hours
|
||||
|
||||
const PARAMETERS = array(
|
||||
self::CONTEXT_BLOG => array(
|
||||
self::PARAM_BLOG_TYPE => array(
|
||||
'name' => 'Blog type',
|
||||
'type' => 'list',
|
||||
'title' => 'Select the blog you want to follow',
|
||||
'values' => array(
|
||||
self::BLOG_TYPE_HOME => 'blog/',
|
||||
self::BLOG_TYPE_COMPANIES_HIRING => 'blog/companies-hiring/',
|
||||
self::BLOG_TYPE_CAREER_ADVICE => 'blog/career-advice/',
|
||||
self::BLOG_TYPE_INTERVIEWS => 'blog/interviews/',
|
||||
self::BLOG_TYPE_GUIDE => 'blog/guide/'
|
||||
)
|
||||
),
|
||||
self::PARAM_BLOG_FULL => array(
|
||||
'name' => 'Full article',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Enable to return the full article for each post'
|
||||
),
|
||||
),
|
||||
self::CONTEXT_REVIEW => array(
|
||||
self::PARAM_REVIEW_COMPANY => array(
|
||||
'name' => 'Company URL',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Paste the company review page URL here!',
|
||||
'exampleValue' => 'https://www.glassdoor.com/Reviews/GitHub-Reviews-E671945.htm'
|
||||
)
|
||||
),
|
||||
self::CONTEXT_GLOBAL => array(
|
||||
self::PARAM_LIMIT => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => -1,
|
||||
'title' => 'Specifies the maximum number of items to return (default: All)'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $host = self::URI; // They redirect without notice :/
|
||||
private $title = '';
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case self::CONTEXT_BLOG:
|
||||
return self::URI . $this->getInput(self::PARAM_BLOG_TYPE);
|
||||
case self::CONTEXT_REVIEW:
|
||||
return $this->filterCompanyURI($this->getInput(self::PARAM_REVIEW_COMPANY));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->title ? $this->title . ' - ' . self::NAME : parent::getName();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Failed loading contents!');
|
||||
|
||||
$this->host = $html->find('link[rel="canonical"]', 0)->href;
|
||||
|
||||
$html = defaultLinkTo($html, $this->host);
|
||||
|
||||
$this->title = $html->find('meta[property="og:title"]', 0)->content;
|
||||
$limit = $this->getInput(self::PARAM_LIMIT);
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case self::CONTEXT_BLOG:
|
||||
$this->collectBlogData($html, $limit);
|
||||
break;
|
||||
case self::CONTEXT_REVIEW:
|
||||
$this->collectReviewData($html, $limit);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectBlogData($html, $limit) {
|
||||
$posts = $html->find('section')
|
||||
or returnServerError('Unable to find blog posts!');
|
||||
|
||||
foreach($posts as $post) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $post->find('header a', 0)->href;
|
||||
$item['title'] = $post->find('header', 0)->plaintext;
|
||||
$item['content'] = $post->find('div[class="excerpt-content"]', 0)->plaintext;
|
||||
$item['enclosures'] = array(
|
||||
$this->getFullSizeImageURI($post->find('div[class*="post-thumb"]', 0)->{'data-original'})
|
||||
);
|
||||
|
||||
// optionally load full articles
|
||||
if($this->getInput(self::PARAM_BLOG_FULL)) {
|
||||
$full_html = getSimpleHTMLDOMCached($item['uri'])
|
||||
or returnServerError('Unable to load full article!');
|
||||
|
||||
$full_html = defaultLinkTo($full_html, $this->host);
|
||||
|
||||
$item['author'] = $full_html->find('a[rel="author"]', 0);
|
||||
$item['content'] = $full_html->find('article', 0);
|
||||
$item['timestamp'] = strtotime($full_html->find('time.updated', 0)->datetime);
|
||||
$item['categories'] = $full_html->find('span[class="post_tag"]');
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if($limit > 0 && count($this->items) >= $limit)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectReviewData($html, $limit) {
|
||||
$reviews = $html->find('#EmployerReviews li[id^="empReview]')
|
||||
or returnServerError('Unable to find reviews!');
|
||||
|
||||
foreach($reviews as $review) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $review->find('a.reviewLink', 0)->href;
|
||||
$item['title'] = $review->find('[class="summary"]', 0)->plaintext;
|
||||
$item['author'] = $review->find('div.author span', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($review->find('time', 0)->datetime);
|
||||
|
||||
$mainText = $review->find('p.mainText', 0)->plaintext;
|
||||
$description = $review->find('div.prosConsAdvice', 0)->innertext;
|
||||
$item['content'] = "<p>{$mainText}</p><p>{$description}</p>";
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if($limit > 0 && count($this->items) >= $limit)
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private function getFullSizeImageURI($uri) {
|
||||
/* Images are scaled for display on the website. The scaling takes place
|
||||
* on the host, who provides images in different sizes.
|
||||
*
|
||||
* For example:
|
||||
* https://www.glassdoor.com/blog/app/uploads/sites/2/GettyImages-982402074-e1538092065712-390x193.jpg
|
||||
*
|
||||
* By removing the size information we receive the full sized image.
|
||||
*
|
||||
* For example:
|
||||
* https://www.glassdoor.com/blog/app/uploads/sites/2/GettyImages-982402074-e1538092065712.jpg
|
||||
*/
|
||||
|
||||
$uri = filter_var($uri, FILTER_SANITIZE_URL);
|
||||
return preg_replace('/(.*)(\-\d+x\d+)(\.jpg)/', '$1$3', $uri);
|
||||
}
|
||||
|
||||
private function filterCompanyURI($uri) {
|
||||
/* Make sure the URI is a valid review page. Unfortunately there is no
|
||||
* simple way to determine if the URI is valid, because of automagic
|
||||
* redirection and strange naming conventions.
|
||||
*/
|
||||
if(!filter_var($uri,
|
||||
FILTER_VALIDATE_URL, FILTER_FLAG_PATH_REQUIRED)) {
|
||||
returnClientError('The specified URL is invalid!');
|
||||
}
|
||||
|
||||
$uri = filter_var($uri, FILTER_SANITIZE_URL);
|
||||
$path = parse_url($uri, PHP_URL_PATH);
|
||||
$parts = explode('/', $path);
|
||||
|
||||
$allowed_strings = array(
|
||||
'de-DE' => 'Bewertungen',
|
||||
'en-AU' => 'Reviews',
|
||||
'nl-BE' => 'Reviews',
|
||||
'fr-BE' => 'Avis',
|
||||
'en-CA' => 'Reviews',
|
||||
'fr-CA' => 'Avis',
|
||||
'fr-FR' => 'Avis',
|
||||
'en-IN' => 'Reviews',
|
||||
'en-IE' => 'Reviews',
|
||||
'nl-NL' => 'Reviews',
|
||||
'de-AT' => 'Bewertungen',
|
||||
'de-CH' => 'Bewertungen',
|
||||
'fr-CH' => 'Avis',
|
||||
'en-GB' => 'Reviews',
|
||||
'en' => 'Reviews'
|
||||
);
|
||||
|
||||
if(!in_array($parts[1], $allowed_strings)) {
|
||||
returnClientError('Please specify a URL pointing to the companies review page!');
|
||||
}
|
||||
|
||||
return $uri;
|
||||
}
|
||||
}
|
88
bridges/GlowficBridge.php
Normal file
88
bridges/GlowficBridge.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
class GlowficBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'l1n';
|
||||
const NAME = 'Glowfic Bridge';
|
||||
const URI = 'https://www.glowfic.com';
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
const DESCRIPTION = 'Returns the latest replies on a glowfic post.';
|
||||
const PARAMETERS = array(
|
||||
'global' => array(),
|
||||
'Thread' => array(
|
||||
'post_id' => array(
|
||||
'name' => 'Post ID',
|
||||
'title' => 'https://www.glowfic.com/posts/<POST ID>',
|
||||
'type' => 'number'
|
||||
),
|
||||
'start_page' => array(
|
||||
'name' => 'Start Page',
|
||||
'title' => 'To start from an offset page',
|
||||
'type' => 'number'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getAPIURI();
|
||||
$metadata = get_headers( $url . '/replies', true ) or returnClientError('Post did not return reply headers.');
|
||||
$metadata['Last-Page'] = ceil( $metadata['Total'] / $metadata['Per-Page'] );
|
||||
if(!is_null($this->getInput('start_page')) &&
|
||||
$this->getInput('start_page') < 1 && $metadata['Last-Page'] - $this->getInput('start_page') > 0) {
|
||||
$first_page = $metadata['Last-Page'] - $this->getInput('start_page');
|
||||
} else if(!is_null($this->getInput('start_page')) && $this->getInput('start_page') <= $metadata['Last-Page']) {
|
||||
$first_page = $this->getInput('start_page');
|
||||
} else {
|
||||
$first_page = 1;
|
||||
}
|
||||
for ($page_offset = $first_page; $page_offset <= $metadata['Last-Page']; $page_offset++) {
|
||||
$jsonContents = getContents($url . '/replies?page=' . $page_offset ) or
|
||||
returnClientError('Could not retrieve replies for page ' . $page_offset . '.');
|
||||
$replies = json_decode($jsonContents);
|
||||
foreach ($replies as $reply) {
|
||||
$item = array();
|
||||
|
||||
$item['content'] = $reply->{'content'};
|
||||
$item['uri'] = $this->getURI() . '?page=' . $page_offset . '#reply-' . $reply->{'id'};
|
||||
if ($reply->{'icon'}) {
|
||||
$item['enclosures'] = array($reply->{'icon'}->{'url'});
|
||||
}
|
||||
$item['author'] = $reply->{'character'}->{'screenname'} . ' (' . $reply->{'character'}->{'name'} . ')';
|
||||
$item['timestamp'] = date('r', strtotime($reply->{'created_at'}));
|
||||
$item['title'] = 'Tag by ' . $reply->{'user'}->{'username'} . ' updated at ' . $reply->{'updated_at'};
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getAPIURI() {
|
||||
$url = parent::getURI() . '/api/v1/posts/' . $this->getInput('post_id');
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
$url = parent::getURI() . '/posts/' . $this->getInput('post_id');
|
||||
return $url;
|
||||
}
|
||||
|
||||
private function getPost() {
|
||||
$url = $this->getAPIURI();
|
||||
$jsonPost = getContents( $url ) or returnClientError('Could not retrieve post metadata.');
|
||||
$post = json_decode($jsonPost);
|
||||
return $post;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('post_id'))) {
|
||||
$post = $this->getPost();
|
||||
return $post->{'subject'} . ' - ' . parent::getName();
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getDescription(){
|
||||
if(!is_null($this->getInput('post_id'))) {
|
||||
$post = $this->getPost();
|
||||
return $post->{'content'};
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'sky';
|
||||
const NAME = 'GoComics Unofficial RSS';
|
||||
const URI = 'http://www.gocomics.com/';
|
||||
const URI = 'https://www.gocomics.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'The Unofficial GoComics RSS';
|
||||
const PARAMETERS = array( array(
|
||||
@@ -18,25 +18,27 @@ class GoComicsBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request GoComics: ' . $this->getURI());
|
||||
|
||||
foreach($html->find('div.comic__container') as $element) {
|
||||
//Get info from first page
|
||||
$author = preg_replace('/By /', '', $html->find('.media-subheading', 0)->plaintext);
|
||||
|
||||
$img = $element->find('.item-comic-image img', 0);
|
||||
$link = $element->find('a.js-item-comic-link', 0);
|
||||
$comic = $img->src;
|
||||
$title = $link->title;
|
||||
$url = $html->find('input.js-copy-link', 0)->value;
|
||||
$date = substr($title, -10);
|
||||
if (empty($title))
|
||||
$title = 'GoComics ' . $this->getInput('comicname') . ' on ' . $date;
|
||||
$date = strtotime($date);
|
||||
$link = self::URI . $html->find('.gc-deck--cta-0', 0)->find('a', 0)->href;
|
||||
for($i = 0; $i < 5; $i++) {
|
||||
|
||||
$item = array();
|
||||
$item['id'] = $url;
|
||||
$item['uri'] = $url;
|
||||
$item['title'] = $title;
|
||||
$item['author'] = preg_replace('/by /', '', $element->find('a.link-blended small', 0)->plaintext);
|
||||
$item['timestamp'] = $date;
|
||||
$item['content'] = '<img src="' . $comic . '" alt="' . $title . '" />';
|
||||
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request GoComics: ' . $link);
|
||||
$imagelink = $page->find('.img-fluid', 1)->src;
|
||||
$date = explode('/', $link);
|
||||
|
||||
$item['id'] = $imagelink;
|
||||
$item['uri'] = $link;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = 'GoComics ' . $this->getInput('comicname');
|
||||
$item['timestamp'] = DateTime::createFromFormat('Ymd', $date[5] . $date[6] . $date[7])->getTimestamp();
|
||||
$item['content'] = '<img src="' . $imagelink . '" />';
|
||||
|
||||
$link = self::URI . $page->find('.js-previous-comic', 0)->href;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -1,111 +0,0 @@
|
||||
<?php
|
||||
class GooglePlusPostBridge extends BridgeAbstract{
|
||||
|
||||
protected $_title;
|
||||
protected $_url;
|
||||
|
||||
const MAINTAINER = 'Grummfy';
|
||||
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
|
||||
)
|
||||
));
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
// get content parsed
|
||||
$html = getSimpleHTMLDOMCached(self::URI . urlencode($username) . '/posts')
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
// get title, url, ... there is a lot of intresting stuff in meta
|
||||
$this->_title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
$this->_url = $html->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||
|
||||
// I don't even know where to start with this discusting html...
|
||||
foreach($html->find('div[jsname=WsjYwc]') as $post) {
|
||||
$item = array();
|
||||
|
||||
$item['author'] = $item['fullname'] = $post->find('div div div div a', 0)->innertext;
|
||||
$item['id'] = $post->find('div div div', 0)->getAttribute('id');
|
||||
$item['avatar'] = $post->find('div img', 0)->src;
|
||||
$item['uri'] = self::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')));
|
||||
}
|
||||
|
||||
// hashtag to treat : https://plus.google.com/explore/tag
|
||||
// $hashtags = array();
|
||||
// foreach($post->find('a.d-s') as $hashtag){
|
||||
// $hashtags[trim($hashtag->plaintext)] = self::URI . $hashtag->href;
|
||||
// }
|
||||
|
||||
$item['content'] = '';
|
||||
|
||||
// avatar display
|
||||
$item['content'] .= '<div style="float:left; margin: 0 0.5em 0.5em 0;"><a href="'
|
||||
. self::URI
|
||||
. urlencode($this->getInput('username'));
|
||||
|
||||
$item['content'] .= '"><img align="top" alt="'
|
||||
. $item['author']
|
||||
. '" src="'
|
||||
. $item['avatar']
|
||||
. '" /></a></div>';
|
||||
|
||||
$content = $post->find('div[jsname=EjRJtf]', 0);
|
||||
// extract plaintext
|
||||
$item['content_simple'] = $content->plaintext;
|
||||
$item['title'] = substr($item['content_simple'], 0, 72) . '...';
|
||||
|
||||
// XXX ugly but I don't have any idea how to do a better stuff,
|
||||
// str_replace on link doesn't work as expected and ask too many checks
|
||||
foreach($content->find('a') as $link) {
|
||||
$hasHttp = strpos($link->href, 'http');
|
||||
$hasDoubleSlash = strpos($link->href, '//');
|
||||
|
||||
if((!$hasHttp && !$hasDoubleSlash)
|
||||
|| (false !== $hasHttp && strpos($link->href, 'http') != 0)
|
||||
|| (false === $hasHttp && false !== $hasDoubleSlash && $hasDoubleSlash != 0)) {
|
||||
// skipp bad link, for some hashtag or other stuff
|
||||
if(strpos($link->href, '/') == 0) {
|
||||
$link->href = substr($link->href, 1);
|
||||
}
|
||||
|
||||
$link->href = self::URI . $link->href;
|
||||
}
|
||||
}
|
||||
$content = $content->innertext;
|
||||
|
||||
$item['content'] .= '<div style="margin-top: -1.5em">' . $content . '</div>';
|
||||
$item['content'] = trim(strip_tags($item['content'], '<a><p><div><img>'));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
return $this->_title ?: 'Google Plus Post Bridge';
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
return $this->_url ?: parent::getURI();
|
||||
}
|
||||
}
|
@@ -17,7 +17,7 @@ class GoogleSearchBridge extends BridgeAbstract {
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'q' => array(
|
||||
'name' => "keyword",
|
||||
'name' => 'keyword',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
@@ -28,7 +28,7 @@ class GoogleSearchBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(self::URI
|
||||
. 'search?q='
|
||||
. urlencode($this->getInput('q'))
|
||||
.'&num=100&complete=0&tbs=qdr:y,sbd:1')
|
||||
. '&num=100&complete=0&tbs=qdr:y,sbd:1')
|
||||
or returnServerError('No results for this query.');
|
||||
|
||||
$emIsRes = $html->find('div[id=ires]', 0);
|
||||
|
62
bridges/GrandComicsDatabaseBridge.php
Normal file
62
bridges/GrandComicsDatabaseBridge.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
class GrandComicsDatabaseBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'corenting';
|
||||
const NAME = 'Grand Comics Database Bridge';
|
||||
const URI = 'https://www.comics.org/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the latest comics added to a series timeline';
|
||||
const PARAMETERS = array( array(
|
||||
'series' => array(
|
||||
'name' => 'Series id (from the timeline URL)',
|
||||
'required' => true,
|
||||
'exampleValue' => '63051',
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$url = self::URI . 'series/' . $this->getInput('series') . '/details/timeline/';
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Error while downloading the website content');
|
||||
|
||||
$table = $html->find('table', 0);
|
||||
$list = array_reverse($table->find('[class^=row_even]'));
|
||||
$seriesName = $html->find('span[id=series_name]', 0)->innertext;
|
||||
|
||||
// Get row headers
|
||||
$rowHeaders = $table->find('th');
|
||||
foreach($list as $article) {
|
||||
|
||||
// Skip empty rows
|
||||
$emptyRow = $article->find('td.empty_month');
|
||||
if (count($emptyRow) != 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rows = $article->find('td');
|
||||
$key_date = $rows[0]->innertext;
|
||||
|
||||
// Get URL too
|
||||
$uri = 'https://www.comics.org' . $article->find('a')[0]->href;
|
||||
|
||||
// Build content
|
||||
$content = '';
|
||||
for($i = 0; $i < count($rowHeaders); $i++) {
|
||||
$headerItem = $rowHeaders[$i]->innertext;
|
||||
$rowItem = $rows[$i]->innertext;
|
||||
$content = $content . $headerItem . ': ' . $rowItem . '<br/>';
|
||||
}
|
||||
|
||||
// Build final item
|
||||
$content = str_replace('href="/', 'href="' . static::URI, $content);
|
||||
$item = array();
|
||||
$item['title'] = $seriesName . ' - ' . $key_date;
|
||||
$item['timestamp'] = strtotime($key_date);
|
||||
$item['content'] = str_get_html($content);
|
||||
$item['uri'] = $uri;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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++;
|
||||
|
102
bridges/HaveIBeenPwnedBridge.php
Normal file
102
bridges/HaveIBeenPwnedBridge.php
Normal file
@@ -0,0 +1,102 @@
|
||||
<?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',
|
||||
)
|
||||
));
|
||||
|
||||
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'] = $breach->find('h3', 0)->plaintext . ' - ' . $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>' . $breach->find('p', 1)->innertext . '<p>';
|
||||
|
||||
$this->breaches[] = $item;
|
||||
}
|
||||
|
||||
$this->orderBreaches();
|
||||
$this->createItems();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user