mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-16 13:34:11 +02:00
Compare commits
1001 Commits
2018-08-07
...
2022-01-20
Author | SHA1 | Date | |
---|---|---|---|
|
5df8bf956a | ||
|
a49767e71b | ||
|
0584fdddde | ||
|
0fdd281fb2 | ||
|
0e17282f60 | ||
|
0f1ec4a879 | ||
|
e9d3d7ba67 | ||
|
3cef35a432 | ||
|
2f10d2345a | ||
|
02a8ae4c62 | ||
|
084a1bcf19 | ||
|
418f951dd1 | ||
|
9dcce0ba1d | ||
|
607d9297ff | ||
|
c65feffb61 | ||
|
f259fa7f9f | ||
|
368a198321 | ||
|
590fdd9f9b | ||
|
799c93a3c6 | ||
|
2f957b6870 | ||
|
12ff697ab0 | ||
|
8530aa54f2 | ||
|
d0ef8aa71d | ||
|
59e77a9e51 | ||
|
37cb4091d4 | ||
|
fc51c6753d | ||
|
71cd15c35d | ||
|
df408fb8bc | ||
|
e545f43a67 | ||
|
ec55e99934 | ||
|
67e33186ce | ||
|
a0bbbd6978 | ||
|
2ee091665a | ||
|
35930ee4e4 | ||
|
e1290aa42c | ||
|
814711e3af | ||
|
28db707587 | ||
|
b48739d0ba | ||
|
b9d92150e1 | ||
|
b395fe2641 | ||
|
4bc534c80f | ||
|
071fdef599 | ||
|
ae6a3227b0 | ||
|
f469489b56 | ||
|
490f556783 | ||
|
192f0278d2 | ||
|
2b634002f2 | ||
|
42379071e9 | ||
|
fd54042ef3 | ||
|
3764348b76 | ||
|
0ba0e2de4e | ||
|
4187d8f4cf | ||
|
1c6532a9d0 | ||
|
547829f971 | ||
|
970bdd45f9 | ||
|
b86ed70376 | ||
|
9254d14f50 | ||
|
8f98e07979 | ||
|
8d0fc54e4d | ||
|
bdf15c3ce0 | ||
|
927b08ed00 | ||
|
87b3aaa550 | ||
|
c445ba6ebb | ||
|
793c55f43d | ||
|
11be390e65 | ||
|
ba5baaf7c9 | ||
|
f0ddd686e3 | ||
|
f6b9864bdd | ||
|
64b7c54bc8 | ||
|
d5a010adcd | ||
|
2a609b39bd | ||
|
dacc586dca | ||
|
8bcf4ebfbf | ||
|
cb111a3ebd | ||
|
42e40e2823 | ||
|
1ddce120ae | ||
|
927cb17dbf | ||
|
ccb2e64fd0 | ||
|
a26408594b | ||
|
324932642d | ||
|
455b5e09a1 | ||
|
bcc15228d8 | ||
|
68d9e2ff24 | ||
|
a5d33615f5 | ||
|
8f634eb4a1 | ||
|
677e4974d1 | ||
|
10c5259493 | ||
|
cb3c055df9 | ||
|
036a3ad245 | ||
|
69ce8106ce | ||
|
8a30480a45 | ||
|
f36832b66e | ||
|
f3f934ed8b | ||
|
7c46c64242 | ||
|
bf1773ed8b | ||
|
4529e3699a | ||
|
f5f0b77805 | ||
|
3637777070 | ||
|
c673917aca | ||
|
877707f7b2 | ||
|
2689f5f7fa | ||
|
bf1cb8fadc | ||
|
716f5ddc0e | ||
|
0ee549f468 | ||
|
4a1e26fd07 | ||
|
e14f647075 | ||
|
34489431b4 | ||
|
126cf1a7fa | ||
|
3050f0ae70 | ||
|
cabf7a748a | ||
|
84450e7e8d | ||
|
971cd9ba39 | ||
|
9fa782105d | ||
|
cb7f5b057f | ||
|
a8d1acfdad | ||
|
b5d9742a21 | ||
|
7dd1a7dccc | ||
|
1f6ad000ce | ||
|
398e175fe0 | ||
|
0de2db853f | ||
|
9399ebb2c6 | ||
|
606972dd3c | ||
|
ecaae735d9 | ||
|
5598fef3cf | ||
|
9c99a1a9c1 | ||
|
75cc52a62c | ||
|
973e49d93e | ||
|
c580219627 | ||
|
a18af3952d | ||
|
15907e2bbd | ||
|
459adf4790 | ||
|
d38bc18232 | ||
|
eec1163fb9 | ||
|
b074abcc0d | ||
|
2ae9793f2c | ||
|
f02d80e141 | ||
|
44e01a4282 | ||
|
63d257d9d0 | ||
|
37cd071453 | ||
|
17f9c44bfc | ||
|
28aaf59007 | ||
|
e8d241e8c9 | ||
|
2b793f04de | ||
|
655e02e3fe | ||
|
a4bd04310f | ||
|
e48617530d | ||
|
3585575d68 | ||
|
e79a02ac2e | ||
|
63ebf5ceec | ||
|
378f78d6eb | ||
|
d7ba7782f3 | ||
|
b24b5ed3ee | ||
|
f06a8ae307 | ||
|
4f7ef212b7 | ||
|
13e9a96cf3 | ||
|
00a24a98be | ||
|
76c38332ee | ||
|
65be209a47 | ||
|
146639ffc9 | ||
|
e1c19461ca | ||
|
ff0c7a9013 | ||
|
b754d14698 | ||
|
3423b3bbe1 | ||
|
5966cc0a9c | ||
|
579bfa669c | ||
|
d61871a45e | ||
|
0c8fabeb11 | ||
|
40c84b5dc3 | ||
|
6f75d07456 | ||
|
b4f809aa44 | ||
|
bcecd70df7 | ||
|
a6c0874b9a | ||
|
9e6f063cfd | ||
|
f904353fd2 | ||
|
3aafd44079 | ||
|
75b85f61e7 | ||
|
07e1e8497c | ||
|
700813e924 | ||
|
5c011c8d90 | ||
|
8d0d08a4d8 | ||
|
55548dcb5f | ||
|
0217b270a7 | ||
|
2ed34f5ebe | ||
|
2448ed41c9 | ||
|
b25674b3a0 | ||
|
2ce1a6365b | ||
|
30aeeb2a0c | ||
|
c294a652a3 | ||
|
a5f2175531 | ||
|
569276f4ef | ||
|
687eb728d4 | ||
|
0521ba5873 | ||
|
3d642971c0 | ||
|
8f086169cc | ||
|
ce34e7eb89 | ||
|
ee5d190391 | ||
|
98352845a1 | ||
|
9e58735b01 | ||
|
771b851b52 | ||
|
809343ed06 | ||
|
e9424f6a08 | ||
|
e5846c03ba | ||
|
6224fbb6a2 | ||
|
eab575dc9d | ||
|
b56637c833 | ||
|
005b22701d | ||
|
43b7621f45 | ||
|
ea289a0cea | ||
|
43acb555e0 | ||
|
3b7e61fb55 | ||
|
fbbd6a02c6 | ||
|
3534193032 | ||
|
81fc8c89d4 | ||
|
ea1de07fe5 | ||
|
2de5ce8387 | ||
|
28f9215913 | ||
|
f927781750 | ||
|
e128ce807a | ||
|
6b870f0c3e | ||
|
5ed161943c | ||
|
1edec1aa45 | ||
|
3c285d50ec | ||
|
3aae00b56a | ||
|
21798e8228 | ||
|
3226a5e31e | ||
|
59bbc9d2e7 | ||
|
2ddd357a62 | ||
|
c302bca1e6 | ||
|
0b3082609d | ||
|
810a2503c9 | ||
|
56b2c516e4 | ||
|
fc81bed717 | ||
|
56eb829a66 | ||
|
7705d097e3 | ||
|
be9df41e07 | ||
|
1e75f9d3d5 | ||
|
0755181555 | ||
|
e6c73a1fe3 | ||
|
5729e069e9 | ||
|
0b494d9c0e | ||
|
c855d5089f | ||
|
6baf64f29e | ||
|
7d4b76be99 | ||
|
ff50e4918c | ||
|
8e4d6d8fdb | ||
|
cc548b16a8 | ||
|
b66026e241 | ||
|
a23d4bd0e6 | ||
|
bde4159a9e | ||
|
3ad138026d | ||
|
d05a8b79fe | ||
|
efe32aad22 | ||
|
0655b3cb39 | ||
|
59082368c7 | ||
|
c8b2c1bf74 | ||
|
b48bc77c22 | ||
|
6af87b2f32 | ||
|
93cdf5e342 | ||
|
164b407f28 | ||
|
2714c3d816 | ||
|
364b5282a3 | ||
|
5e4f3c351e | ||
|
a332a5a414 | ||
|
45e2f385b3 | ||
|
0a1ff10a52 | ||
|
645a8f62c6 | ||
|
64ec488f70 | ||
|
7b6ff78623 | ||
|
82acbbb421 | ||
|
84d5daaa03 | ||
|
712f60e910 | ||
|
55015f80cf | ||
|
f90c6b5bb9 | ||
|
ff98efe8dc | ||
|
fe166d0216 | ||
|
d3455dd18a | ||
|
47dc26c775 | ||
|
3df2de4c6f | ||
|
01985b7af7 | ||
|
80cc88ba78 | ||
|
2bb99c4448 | ||
|
3a29347e60 | ||
|
d299adb827 | ||
|
cf606a3a6b | ||
|
6c244f4d9b | ||
|
d6f277d029 | ||
|
ab8e89a97f | ||
|
747bb6ad9c | ||
|
d33e090fe1 | ||
|
fec52418d5 | ||
|
bb51a0d212 | ||
|
68dd2d745f | ||
|
46abc18e87 | ||
|
e00bbe353f | ||
|
c21a805cb4 | ||
|
3b36c413e5 | ||
|
94576c3053 | ||
|
25cff9c07b | ||
|
07c71b3b36 | ||
|
859053ef7a | ||
|
73287f536b | ||
|
0b1e592a5e | ||
|
ef54a78430 | ||
|
4b8c3b9d36 | ||
|
c642652fea | ||
|
f0e6298cab | ||
|
45e247b9d0 | ||
|
66a009b8fb | ||
|
efd1abfab1 | ||
|
8b173b8874 | ||
|
90e9c9962a | ||
|
01cc32a0cc | ||
|
dc36b425cd | ||
|
268ddf1382 | ||
|
8144488a9e | ||
|
062dd7f8a5 | ||
|
be089702f0 | ||
|
c71fad4a4a | ||
|
5be251a66e | ||
|
7709b8d662 | ||
|
f5916a2f74 | ||
|
a33088ca99 | ||
|
78facbcb83 | ||
|
d5a75a2545 | ||
|
25698d182c | ||
|
9e74cc64ed | ||
|
78298385d0 | ||
|
976445b490 | ||
|
3ad126cdf2 | ||
|
e87b868307 | ||
|
23c61f5f84 | ||
|
22a01f1093 | ||
|
98ff5a095c | ||
|
e4c4ae8245 | ||
|
124631df73 | ||
|
06891ae35f | ||
|
c4422bdbb5 | ||
|
a1dd98ff82 | ||
|
25f0d3b877 | ||
|
9a66227a79 | ||
|
8047041963 | ||
|
fa74d3728b | ||
|
8233497611 | ||
|
71745116e1 | ||
|
36fc4822dd | ||
|
868d3f600d | ||
|
f4affe1833 | ||
|
63a4db7e86 | ||
|
f48909b84e | ||
|
ca88096f1f | ||
|
1044952987 | ||
|
119f4bdec5 | ||
|
e617d9f728 | ||
|
5a43db4fb5 | ||
|
badb5313b7 | ||
|
5eeda8dd52 | ||
|
413ae3cef6 | ||
|
604d527ac7 | ||
|
cccd390b0f | ||
|
223337d62d | ||
|
066e42e99a | ||
|
fbfc82b0b7 | ||
|
00dd81a8aa | ||
|
e0ac9972ee | ||
|
f2de5aecc7 | ||
|
0fd7021030 | ||
|
3ec32bb6c2 | ||
|
ec7ef8f502 | ||
|
7b73f3217f | ||
|
7c71377af0 | ||
|
c2559ff71f | ||
|
366d2d66b3 | ||
|
7b63da522f | ||
|
0705a2e7bb | ||
|
84616f53bf | ||
|
a981450ae0 | ||
|
d39741c296 | ||
|
3179c1e884 | ||
|
c9e5f6c9dd | ||
|
6b6974d115 | ||
|
96e58d4c94 | ||
|
f0363ba03b | ||
|
90147fc45c | ||
|
a3b4bd2d08 | ||
|
e102353ab8 | ||
|
a54eb88ee1 | ||
|
1584636e5b | ||
|
fe83d763a3 | ||
|
480694e819 | ||
|
8697e1e1a2 | ||
|
1ab7e493a8 | ||
|
e5303efba3 | ||
|
5bd07723ad | ||
|
00dbde2c24 | ||
|
a00e75b71c | ||
|
f040e4dc9c | ||
|
182e9e7b41 | ||
|
275662b8d4 | ||
|
f52eb43f8c | ||
|
2450f80823 | ||
|
45287e6853 | ||
|
830f57f607 | ||
|
6a90a9d33f | ||
|
46b9879c08 | ||
|
1343dbe97a | ||
|
2175a4d08b | ||
|
ad661c4c91 | ||
|
ba8c4623ed | ||
|
ba43c87952 | ||
|
595b87946d | ||
|
99d4e1a43d | ||
|
477de4e2df | ||
|
246470da18 | ||
|
df9f7eb778 | ||
|
375831f516 | ||
|
e518936be7 | ||
|
583dfb4958 | ||
|
2de45b163e | ||
|
48b0164676 | ||
|
4b3c3c58d2 | ||
|
60768b4885 | ||
|
02dd778124 | ||
|
5b63121e92 | ||
|
49019a843f | ||
|
d65714fa47 | ||
|
8161829ad5 | ||
|
7f35fc9f6b | ||
|
3bc8c9468a | ||
|
1df3598a74 | ||
|
5f64fe2516 | ||
|
50eee7e7b3 | ||
|
c0df9815c7 | ||
|
46d5895d1d | ||
|
7c16aaf303 | ||
|
cdc1d9c9ba | ||
|
6bc83310b9 | ||
|
c8d5c85c76 | ||
|
d1e4bd7285 | ||
|
1022b5fdf9 | ||
|
e8536ac1b2 | ||
|
a0afe36d56 | ||
|
0b80f9d61c | ||
|
424075981f | ||
|
c334df91ec | ||
|
f2346fb33e | ||
|
8a21fd1476 | ||
|
2ac44172ac | ||
|
4c78721f03 | ||
|
04be85996d | ||
|
59be6bded2 | ||
|
46873e14fe | ||
|
0f01cc97a4 | ||
|
a70e00a76d | ||
|
f0260c62c3 | ||
|
fc5a1526ca | ||
|
4c0e234479 | ||
|
0eab63d728 | ||
|
b0884e9158 | ||
|
b4581418d4 | ||
|
af1566f40d | ||
|
529e0d0cca | ||
|
a3532804ac | ||
|
8c19146d29 | ||
|
2a3d5865ad | ||
|
4d36c9dc30 | ||
|
a2e47a88c3 | ||
|
b09f50853f | ||
|
9b5bf565b3 | ||
|
5cc956367f | ||
|
548e28249b | ||
|
684c69b0cd | ||
|
3dae4e0801 | ||
|
4622d9be1e | ||
|
76183dcd44 | ||
|
50b234d893 | ||
|
af48f36fd2 | ||
|
7f6ca23e8f | ||
|
1daef22a3d | ||
|
c694810d9a | ||
|
f12f6a2dba | ||
|
b1be45df6c | ||
|
b4f393a5cc | ||
|
29126ebe29 | ||
|
50c971d545 | ||
|
7aba7992aa | ||
|
48ebed7b38 | ||
|
ccef6b95ad | ||
|
dd5da99a30 | ||
|
2ff27b92ff | ||
|
b47189921f | ||
|
f1d3e8c9c9 | ||
|
53fbd2a5a0 | ||
|
3254a4d7bc | ||
|
52d2d21da5 | ||
|
abb74f056c | ||
|
25548b6757 | ||
|
cfe433e9e2 | ||
|
0dfc4ea2c5 | ||
|
38960df180 | ||
|
b440a6fdc6 | ||
|
48d0385653 | ||
|
b68c0e0df8 | ||
|
f27b267614 | ||
|
8bff63d9c6 | ||
|
2b4a030158 | ||
|
6a99904e64 | ||
|
f3c687604f | ||
|
a86a94555d | ||
|
acc0787b00 | ||
|
c8992650a1 | ||
|
f9f511a849 | ||
|
990719d614 | ||
|
b6be18d585 | ||
|
cf525c964a | ||
|
52a4f0860c | ||
|
21b27a1042 | ||
|
2eee535171 | ||
|
da51fc065f | ||
|
e032705c9a | ||
|
be27bc9250 | ||
|
75edc1b2b7 | ||
|
c9ea53806d | ||
|
2bb9480555 | ||
|
eb942bc498 | ||
|
5a0ea423c4 | ||
|
2120cc42fb | ||
|
5067501661 | ||
|
aea8484ccc | ||
|
6b9394dc78 | ||
|
4b51d42b8c | ||
|
d3fbf0d872 | ||
|
41a8eb74a1 | ||
|
7e6c58b67a | ||
|
a31e518a07 | ||
|
50162f52b6 | ||
|
c0edf6e424 | ||
|
2ea8d73ac1 | ||
|
465cd8c768 | ||
|
73f4bc078e | ||
|
1add201d3b | ||
|
09113c2594 | ||
|
c39e642877 | ||
|
e2460ead18 | ||
|
60c1339612 | ||
|
d324aa5da1 | ||
|
6f24987601 | ||
|
54fb29d443 | ||
|
ebe463dd08 | ||
|
987f42d6d4 | ||
|
fa8253c8bf | ||
|
e4444e6432 | ||
|
3769850ba3 | ||
|
89e3da0b6f | ||
|
99d4571c6b | ||
|
69acc6228a | ||
|
5e2f0fb626 | ||
|
372461b1a3 | ||
|
1591e18027 | ||
|
e2bca5bb05 | ||
|
7926ffad73 | ||
|
7ff97c0c7b | ||
|
1989252608 | ||
|
91e73b00b5 | ||
|
5c6c79baf4 | ||
|
99d1343045 | ||
|
14e6dbb645 | ||
|
fc8421ed50 | ||
|
2460b67886 | ||
|
705b9daa0b | ||
|
1ada9c26f8 | ||
|
55e1703741 | ||
|
849eaeb50e | ||
|
aeca4cfd60 | ||
|
686f21bc50 | ||
|
8dd8be9694 | ||
|
dfa9c651cd | ||
|
6d6d6037a3 | ||
|
2559dbbf49 | ||
|
de53120843 | ||
|
b1b7e4edce | ||
|
b27487ace0 | ||
|
d005acca83 | ||
|
93de8c239b | ||
|
75b0213684 | ||
|
f76a23f0a5 | ||
|
e4e04a7865 | ||
|
da339fd5cc | ||
|
ba116d9ab6 | ||
|
ea08445946 | ||
|
ade09b2aad | ||
|
28d46b6721 | ||
|
1efb7c7bce | ||
|
d34411137f | ||
|
70542686bb | ||
|
edf10be93a | ||
|
a725fdd315 | ||
|
84ba0c4a9e | ||
|
c17b864242 | ||
|
5ff3d0121c | ||
|
f00a054e0f | ||
|
5a9519967b | ||
|
17f587fcbe | ||
|
f28cbecc02 | ||
|
84450371b5 | ||
|
69dd33ac82 | ||
|
95388cdf44 | ||
|
b74dda7af9 | ||
|
ca1a5feba5 | ||
|
69a0498732 | ||
|
3d231a417f | ||
|
35bd706391 | ||
|
0e30468e0f | ||
|
ccf375e917 | ||
|
946a99d334 | ||
|
e2e0ced055 | ||
|
d4e867f240 | ||
|
b0a780acda | ||
|
1814116d67 | ||
|
d89326fe2d | ||
|
62198ecfa2 | ||
|
94e4ef8f27 | ||
|
6c4098d655 | ||
|
468d8be72d | ||
|
ed539bacf9 | ||
|
82a9bb5b1c | ||
|
15c374e317 | ||
|
052844f5e1 | ||
|
014b698f67 | ||
|
5656792cee | ||
|
66c5b732cf | ||
|
b889e867fd | ||
|
b519d350bf | ||
|
2a254855d8 | ||
|
72bcc173eb | ||
|
4a60f05fd6 | ||
|
84d48d5614 | ||
|
7cf898b5af | ||
|
16bd2aec7a | ||
|
3d87ecbf8c | ||
|
2cd310c025 | ||
|
b764204c3a | ||
|
a9e2574016 | ||
|
e3f6e1c6db | ||
|
8150a73922 | ||
|
a2f3866383 | ||
|
a3446ae77b | ||
|
75359bc11b | ||
|
fe103974f5 | ||
|
33c16f8be5 | ||
|
21d3bf3b60 | ||
|
3b8f3da09d | ||
|
f9c4a84c25 | ||
|
7b8dd93a8e | ||
|
8f5151b222 | ||
|
98c2530984 | ||
|
90bf90d167 | ||
|
6feda2220e | ||
|
92775abe11 | ||
|
24cdeabed8 | ||
|
380fdf2e40 | ||
|
50c90eb5df | ||
|
d9ee9e272e | ||
|
4ba0d8bebe | ||
|
c9b0cd1315 | ||
|
2dc0c36e9b | ||
|
0aa8858551 | ||
|
966d450d27 | ||
|
291e8c2a23 | ||
|
b6943de0ca | ||
|
b9bbc9bdda | ||
|
835e3b1163 | ||
|
3212156925 | ||
|
281eaacaeb | ||
|
18d5ef192c | ||
|
6293c3d33d | ||
|
835af1faf1 | ||
|
88aae6fd95 | ||
|
684558e276 | ||
|
d7094b7feb | ||
|
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 |
@@ -1,8 +1,14 @@
|
||||
.git
|
||||
.gitattributes
|
||||
.github/*
|
||||
.travis.yml
|
||||
cache/*
|
||||
CONTRIBUTING.md
|
||||
DEBUG
|
||||
Dockerfile
|
||||
whitelist.txt
|
||||
phpcompatibility.xml
|
||||
phpcs.xml
|
||||
CHANGELOG.md
|
||||
CONTRIBUTING.md
|
||||
phpcs.xml
|
||||
scalingo.json
|
||||
tests/*
|
||||
whitelist.txt
|
67
.gitattributes
vendored
67
.gitattributes
vendored
@@ -10,13 +10,60 @@
|
||||
*.dbproj merge=union
|
||||
|
||||
# Standard to msysgit
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
*.doc diff=astextplain
|
||||
*.DOC diff=astextplain
|
||||
*.docx diff=astextplain
|
||||
*.DOCX diff=astextplain
|
||||
*.dot diff=astextplain
|
||||
*.DOT diff=astextplain
|
||||
*.pdf diff=astextplain
|
||||
*.PDF diff=astextplain
|
||||
*.rtf diff=astextplain
|
||||
*.RTF diff=astextplain
|
||||
|
||||
# Ignore files in git archive (i.e. GitHub release builds)
|
||||
|
||||
## Docker
|
||||
Dockerfile export-ignore
|
||||
.dockerignore export-ignore
|
||||
|
||||
## Travis
|
||||
.travis.yml export-ignore
|
||||
|
||||
## GitHub
|
||||
.github/ export-ignore
|
||||
|
||||
## Git
|
||||
.gitattributes export-ignore
|
||||
.gitignore export-ignore
|
||||
|
||||
## Scalingo
|
||||
scalingo.json export-ignore
|
||||
|
||||
## RSS-Bridge
|
||||
phpunit.xml export-ignore
|
||||
phpcs.xml export-ignore
|
||||
phpcompatibility.xml export-ignore
|
||||
tests/ export-ignore
|
||||
cache/.gitkeep export-ignore
|
||||
bridges/DemoBridge.php export-ignore
|
||||
bridges/FeedExpanderExampleBridge.php export-ignore
|
||||
|
||||
## Composer
|
||||
#
|
||||
# Keep the following lines commented out. Heroku does
|
||||
# not function if the composer files are ignored during
|
||||
# export. For more information see
|
||||
# https://github.com/rss-bridge/rss-bridge/issues/1165
|
||||
#
|
||||
# composer.json export-ignore
|
||||
# composer.lock export-ignore
|
||||
|
||||
## Heroku
|
||||
#
|
||||
# Keep the following line commented out. Heroku does
|
||||
# not function if app.json is ignored during export.
|
||||
# For more information see
|
||||
# https://github.com/rss-bridge/rss-bridge/issues/1165
|
||||
#
|
||||
# app.json export-ignore
|
||||
|
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.
|
61
.github/workflows/dockerbuild.yml
vendored
Normal file
61
.github/workflows/dockerbuild.yml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Build Image on Commit and Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
tags:
|
||||
- '20*'
|
||||
|
||||
env:
|
||||
DOCKERHUB_SLUG: rssbridge/rss-bridge
|
||||
GHCR_SLUG: ghcr.io/rss-bridge/rss-bridge
|
||||
|
||||
jobs:
|
||||
bake:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
-
|
||||
name: Checkout
|
||||
uses: actions/checkout@v2.3.4
|
||||
-
|
||||
name: Docker meta
|
||||
id: docker_meta
|
||||
uses: docker/metadata-action@v3.5.0
|
||||
with:
|
||||
images: |
|
||||
${{ env.DOCKERHUB_SLUG }}
|
||||
${{ env.GHCR_SLUG }}
|
||||
tags: |
|
||||
type=raw,value=latest
|
||||
type=sha
|
||||
type=ref,event=tag,enable=${{ startsWith(github.ref, 'refs/tags/20') }}
|
||||
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/20') }}
|
||||
-
|
||||
name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
-
|
||||
name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1.6.0
|
||||
-
|
||||
name: Login to DockerHub
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
-
|
||||
name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@v1.10.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
-
|
||||
name: Build and push
|
||||
uses: docker/bake-action@v1.6.0
|
||||
with:
|
||||
files: |
|
||||
./docker-bake.hcl
|
||||
${{ steps.docker_meta.outputs.bake-file }}
|
||||
targets: image-all
|
||||
push: true
|
35
.github/workflows/lint.yml
vendored
Normal file
35
.github/workflows/lint.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Lint
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
phpcs:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.1', '7.2', '7.3', '7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
tools: phpcs
|
||||
- run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
|
||||
|
||||
phpcompatibility:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.1', '7.4']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require dealerdirect/phpcodesniffer-composer-installer
|
||||
- run: composer global require phpcompatibility/php-compatibility
|
||||
- run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p
|
35
.github/workflows/tests.yml
vendored
Normal file
35
.github/workflows/tests.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ master ]
|
||||
pull_request:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
# TODO: return back when fixed https://github.com/RSS-Bridge/rss-bridge/issues/2391
|
||||
# phpunit7:
|
||||
# runs-on: ubuntu-20.04
|
||||
# strategy:
|
||||
# matrix:
|
||||
# php-versions: ['7.1', '7.2', '7.3']
|
||||
# steps:
|
||||
# - uses: actions/checkout@v2
|
||||
# - uses: shivammathur/setup-php@v2
|
||||
# with:
|
||||
# php-version: ${{ matrix.php-versions }}
|
||||
# - run: composer global require phpunit/phpunit ^7
|
||||
# - run: phpunit --configuration=phpunit.xml --include-path=lib/
|
||||
|
||||
phpunit8:
|
||||
runs-on: ubuntu-20.04
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4', '8.0', '8.1']
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: ${{ matrix.php-versions }}
|
||||
- run: composer global require phpunit/phpunit ^8
|
||||
- run: phpunit --configuration=phpunit.xml --include-path=lib/
|
7
.gitignore
vendored
7
.gitignore
vendored
@@ -236,3 +236,10 @@ config.ini.php
|
||||
|
||||
#Builder
|
||||
.buildconfig
|
||||
|
||||
#Auth
|
||||
.htaccess
|
||||
.htpasswd
|
||||
|
||||
#Crawler
|
||||
robots.txt
|
||||
|
32
.travis.yml
32
.travis.yml
@@ -1,32 +0,0 @@
|
||||
dist: trusty
|
||||
sudo: false
|
||||
language: php
|
||||
|
||||
install:
|
||||
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
||||
composer global require squizlabs/PHP_CodeSniffer;
|
||||
else
|
||||
pear channel-update pear.php.net;
|
||||
pear install PHP_CodeSniffer;
|
||||
fi
|
||||
|
||||
script:
|
||||
- phpenv rehash
|
||||
- if [[ $TRAVIS_PHP_VERSION == "hhvm" ]]; then
|
||||
/home/travis/.composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
else
|
||||
phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
|
||||
fi
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
|
||||
include:
|
||||
- php: 5.6
|
||||
- php: 7.0
|
||||
- php: hhvm
|
||||
- php: nightly
|
||||
|
||||
allow_failures:
|
||||
- php: hhvm
|
||||
- php: nightly
|
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);
|
||||
}
|
||||
```
|
||||
|
25
Dockerfile
25
Dockerfile
@@ -1,5 +1,24 @@
|
||||
FROM ulsmith/alpine-apache-php7
|
||||
FROM php:7-apache
|
||||
|
||||
COPY ./ /app/public/
|
||||
LABEL description="RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one."
|
||||
LABEL repository="https://github.com/RSS-Bridge/rss-bridge"
|
||||
LABEL website="https://github.com/RSS-Bridge/rss-bridge"
|
||||
|
||||
RUN chown -R apache:root /app/public
|
||||
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 --no-install-recommends install \
|
||||
zlib1g-dev \
|
||||
libmemcached-dev \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& pecl install memcached \
|
||||
&& docker-php-ext-enable memcached \
|
||||
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
|
||||
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
|
||||
&& sed -ri -e 's/(MinProtocol\s*=\s*)TLSv1\.2/\1None/' /etc/ssl/openssl.cnf \
|
||||
&& sed -ri -e 's/(CipherString\s*=\s*DEFAULT)@SECLEVEL=2/\1/' /etc/ssl/openssl.cnf
|
||||
|
||||
COPY --chown=www-data:www-data ./ /app/
|
||||
|
||||
CMD ["/app/docker-entrypoint.sh"]
|
||||
|
349
README.md
349
README.md
@@ -1,22 +1,28 @@
|
||||
rss-bridge
|
||||

|
||||
===
|
||||
[](UNLICENSE) [](https://github.com/rss-bridge/rss-bridge/releases/latest) [](https://travis-ci.org/RSS-Bridge/rss-bridge) [](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||
[](UNLICENSE)
|
||||
[](https://github.com/rss-bridge/rss-bridge/releases/latest)
|
||||
[](https://web.libera.chat/#rssbridge)
|
||||
[](https://matrix.to/#/#rssbridge:libera.chat)
|
||||
[](https://github.com/RSS-Bridge/rss-bridge/actions)
|
||||
[](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||
|
||||
rss-bridge is a PHP project capable of generating ATOM feeds for websites which don't have one.
|
||||
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode.
|
||||
|
||||
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
|
||||
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
|
||||
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
|
||||
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
|
||||
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/)
|
||||
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/) (There is an [issue](https://github.com/RSS-Bridge/rss-bridge/issues/2047) for public instances)
|
||||
* `FlickrExplore` : [Latest interesting images](http://www.flickr.com/explore) from Flickr
|
||||
* `GooglePlus` : Most recent posts of user timeline
|
||||
* `GoogleSearch` : Most recent results from Google Search
|
||||
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
|
||||
* `Instagram`: Most recent photos from an Instagram user
|
||||
* `Instagram`: Most recent photos from an Instagram user (There is an [issue](https://github.com/RSS-Bridge/rss-bridge/issues/1891) for public instances)
|
||||
* `OpenClassrooms`: Lastest tutorials from [fr.openclassrooms.com](http://fr.openclassrooms.com/)
|
||||
* `Pinterest`: Most recent photos from user or search
|
||||
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
|
||||
@@ -25,106 +31,297 @@ 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`)
|
||||
* `curl` extension enabled in PHP config (`php.ini`)
|
||||
RSS-Bridge requires PHP 7.1 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)
|
||||
- [`filter`](https://secure.php.net/manual/en/book.filter.php)
|
||||
- [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
|
||||
|
||||
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
|
||||
|
||||
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):
|
||||
./contrib/prepare_release/fetch_contributors.php
|
||||
-->
|
||||
|
||||
* [16mhz](https://github.com/16mhz)
|
||||
* [adamchainz](https://github.com/adamchainz)
|
||||
* [Ahiles3005](https://github.com/Ahiles3005)
|
||||
* [akirk](https://github.com/akirk)
|
||||
* [Albirew](https://github.com/Albirew)
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alex73](https://github.com/alex73)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [AmauryCarrade](https://github.com/AmauryCarrade)
|
||||
* [AntoineTurmel](https://github.com/AntoineTurmel)
|
||||
* [arnd-s](https://github.com/arnd-s)
|
||||
* [ArthurHoaro](https://github.com/ArthurHoaro)
|
||||
* [Astalaseven](https://github.com/Astalaseven)
|
||||
* [Astyan-42](https://github.com/Astyan-42)
|
||||
* [AxorPL](https://github.com/AxorPL)
|
||||
* [ayacoo](https://github.com/ayacoo)
|
||||
* [az5he6ch](https://github.com/az5he6ch)
|
||||
* [b1nj](https://github.com/b1nj)
|
||||
* [benasse](https://github.com/benasse)
|
||||
* [Binnette](https://github.com/Binnette)
|
||||
* [Bockiii](https://github.com/Bockiii)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
* [Chouchen](https://github.com/Chouchen)
|
||||
* [ckiw](https://github.com/ckiw)
|
||||
* [cn-tools](https://github.com/cn-tools)
|
||||
* [cnlpete](https://github.com/cnlpete)
|
||||
* [corenting](https://github.com/corenting)
|
||||
* [couraudt](https://github.com/couraudt)
|
||||
* [csisoap](https://github.com/csisoap)
|
||||
* [da2x](https://github.com/da2x)
|
||||
* [dabenzel](https://github.com/dabenzel)
|
||||
* [Daiyousei](https://github.com/Daiyousei)
|
||||
* [dawidsowa](https://github.com/dawidsowa)
|
||||
* [DevonHess](https://github.com/DevonHess)
|
||||
* [dhuschde](https://github.com/dhuschde)
|
||||
* [disk0x](https://github.com/disk0x)
|
||||
* [DJCrashdummy](https://github.com/DJCrashdummy)
|
||||
* [Djuuu](https://github.com/Djuuu)
|
||||
* [DnAp](https://github.com/DnAp)
|
||||
* [dominik-th](https://github.com/dominik-th)
|
||||
* [Draeli](https://github.com/Draeli)
|
||||
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
|
||||
* [drego85](https://github.com/drego85)
|
||||
* [drklee3](https://github.com/drklee3)
|
||||
* [dvikan](https://github.com/dvikan)
|
||||
* [em92](https://github.com/em92)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
* [f0086](https://github.com/f0086)
|
||||
* [fanch317](https://github.com/fanch317)
|
||||
* [fatuuse](https://github.com/fatuuse)
|
||||
* [fivefilters](https://github.com/fivefilters)
|
||||
* [floviolleau](https://github.com/floviolleau)
|
||||
* [fluffy-critter](https://github.com/fluffy-critter)
|
||||
* [fmachen](https://github.com/fmachen)
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [fulmeek](https://github.com/fulmeek)
|
||||
* [ggiessen](https://github.com/ggiessen)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [girlpunk](https://github.com/girlpunk)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
* [gloony](https://github.com/gloony)
|
||||
* [GregThib](https://github.com/GregThib)
|
||||
* [griffaurel](https://github.com/griffaurel)
|
||||
* [Grummfy](https://github.com/Grummfy)
|
||||
* [gsantner](https://github.com/gsantner)
|
||||
* [guigot](https://github.com/guigot)
|
||||
* [hollowleviathan](https://github.com/hollowleviathan)
|
||||
* [hpacleb](https://github.com/hpacleb)
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [husim0](https://github.com/husim0)
|
||||
* [IceWreck](https://github.com/IceWreck)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [jacquesh](https://github.com/jacquesh)
|
||||
* [JasonGhent](https://github.com/JasonGhent)
|
||||
* [jcgoette](https://github.com/jcgoette)
|
||||
* [jdesgats](https://github.com/jdesgats)
|
||||
* [jdigilio](https://github.com/jdigilio)
|
||||
* [JeremyRand](https://github.com/JeremyRand)
|
||||
* [JimDog546](https://github.com/JimDog546)
|
||||
* [jNullj](https://github.com/jNullj)
|
||||
* [Jocker666z](https://github.com/Jocker666z)
|
||||
* [johnnygroovy](https://github.com/johnnygroovy)
|
||||
* [johnpc](https://github.com/johnpc)
|
||||
* [joni1993](https://github.com/joni1993)
|
||||
* [KamaleiZestri](https://github.com/KamaleiZestri)
|
||||
* [klimplant](https://github.com/klimplant)
|
||||
* [kolarcz](https://github.com/kolarcz)
|
||||
* [kranack](https://github.com/kranack)
|
||||
* [kraoc](https://github.com/kraoc)
|
||||
* [l1n](https://github.com/l1n)
|
||||
* [laBecasse](https://github.com/laBecasse)
|
||||
* [lagaisse](https://github.com/lagaisse)
|
||||
* [lalannev](https://github.com/lalannev)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [Leomaradan](https://github.com/Leomaradan)
|
||||
* [leyrer](https://github.com/leyrer)
|
||||
* [liamka](https://github.com/liamka)
|
||||
* [Limero](https://github.com/Limero)
|
||||
* [LogMANOriginal](https://github.com/LogMANOriginal)
|
||||
* [lorenzos](https://github.com/lorenzos)
|
||||
* [lukasklinger](https://github.com/lukasklinger)
|
||||
* [m0zes](https://github.com/m0zes)
|
||||
* [Mar-Koeh](https://github.com/Mar-Koeh)
|
||||
* [marcus-at-localhost](https://github.com/marcus-at-localhost)
|
||||
* [marius851000](https://github.com/marius851000)
|
||||
* [matthewseal](https://github.com/matthewseal)
|
||||
* [mcbyte-it](https://github.com/mcbyte-it)
|
||||
* [mdemoss](https://github.com/mdemoss)
|
||||
* [melangue](https://github.com/melangue)
|
||||
* [metaMMA](https://github.com/metaMMA)
|
||||
* [mibe](https://github.com/mibe)
|
||||
* [mightymt](https://github.com/mightymt)
|
||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||
* [Monocularity](https://github.com/Monocularity)
|
||||
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mschwld](https://github.com/mschwld)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [Niehztog](https://github.com/Niehztog)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [obsiwitch](https://github.com/obsiwitch)
|
||||
* [Ololbu](https://github.com/Ololbu)
|
||||
* [ORelio](https://github.com/ORelio)
|
||||
* [otakuf](https://github.com/otakuf)
|
||||
* [Park0](https://github.com/Park0)
|
||||
* [Paroleen](https://github.com/Paroleen)
|
||||
* [PaulVayssiere](https://github.com/PaulVayssiere)
|
||||
* [pellaeon](https://github.com/pellaeon)
|
||||
* [PeterDaveHello](https://github.com/PeterDaveHello)
|
||||
* [Peterr-K](https://github.com/Peterr-K)
|
||||
* [Piranhaplant](https://github.com/Piranhaplant)
|
||||
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
|
||||
* [pitchoule](https://github.com/pitchoule)
|
||||
* [pmaziere](https://github.com/pmaziere)
|
||||
* [Pofilo](https://github.com/Pofilo)
|
||||
* [prysme01](https://github.com/prysme01)
|
||||
* [Qluxzz](https://github.com/Qluxzz)
|
||||
* [quentinus95](https://github.com/quentinus95)
|
||||
* [rakoo](https://github.com/rakoo)
|
||||
* [RawkBob](https://github.com/RawkBob)
|
||||
* [regisenguehard](https://github.com/regisenguehard)
|
||||
* [Riduidel](https://github.com/Riduidel)
|
||||
* [rogerdc](https://github.com/rogerdc)
|
||||
* [Roliga](https://github.com/Roliga)
|
||||
* [ronansalmon](https://github.com/ronansalmon)
|
||||
* [rremizov](https://github.com/rremizov)
|
||||
* [sal0max](https://github.com/sal0max)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [shutosg](https://github.com/shutosg)
|
||||
* [simon816](https://github.com/simon816)
|
||||
* [Simounet](https://github.com/Simounet)
|
||||
* [somini](https://github.com/somini)
|
||||
* [SpangleLabs](https://github.com/SpangleLabs)
|
||||
* [squeek502](https://github.com/squeek502)
|
||||
* [stjohnjohnson](https://github.com/stjohnjohnson)
|
||||
* [Stopka](https://github.com/Stopka)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
* [sublimz](https://github.com/sublimz)
|
||||
* [sunchaserinfo](https://github.com/sunchaserinfo)
|
||||
* [SuperSandro2000](https://github.com/SuperSandro2000)
|
||||
* [sysadminstory](https://github.com/sysadminstory)
|
||||
* [t0stiman](https://github.com/t0stiman)
|
||||
* [tameroski](https://github.com/tameroski)
|
||||
* [teromene](https://github.com/teromene)
|
||||
* [tgkenney](https://github.com/tgkenney)
|
||||
* [thefranke](https://github.com/thefranke)
|
||||
* [ThePadawan](https://github.com/ThePadawan)
|
||||
* [TheRadialActive](https://github.com/TheRadialActive)
|
||||
* [theScrabi](https://github.com/theScrabi)
|
||||
* [thezeroalpha](https://github.com/thezeroalpha)
|
||||
* [timendum](https://github.com/timendum)
|
||||
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
|
||||
* [triatic](https://github.com/triatic)
|
||||
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
|
||||
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||
* [wtuuju](https://github.com/wtuuju)
|
||||
* [xurxof](https://github.com/xurxof)
|
||||
* [yamanq](https://github.com/yamanq)
|
||||
* [yardenac](https://github.com/yardenac)
|
||||
* [ymeister](https://github.com/ymeister)
|
||||
* [yue-dongchen](https://github.com/yue-dongchen)
|
||||
* [ZeNairolf](https://github.com/ZeNairolf)
|
||||
|
||||
Licenses
|
||||
===
|
||||
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:
|
||||
|
||||
* [`Parsedown`](https://github.com/erusev/parsedown) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
* [`PHP Simple HTML DOM Parser`](http://simplehtmldom.sourceforge.net/) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
* [`php-urljoin`](https://github.com/fluffy-critter/php-urljoin) licensed under the [MIT License](http://opensource.org/licenses/MIT)
|
||||
|
||||
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 +330,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.
|
||||
We are rebuilding bridges you have willfully destroyed.
|
||||
|
||||
Get your shit together: Put RSS/ATOM back in.
|
||||
Get your shit together: Put RSS/Atom back in.
|
||||
|
136
actions/ConnectivityAction.php
Normal file
136
actions/ConnectivityAction.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
/**
|
||||
* Checks if the website for a given bridge is reachable.
|
||||
*
|
||||
* **Remarks**
|
||||
* - This action is only available in debug mode.
|
||||
* - Returns the bridge status as Json-formatted string.
|
||||
* - Returns an error if the bridge is not whitelisted.
|
||||
* - Returns a responsive web page that automatically checks all whitelisted
|
||||
* bridges (using JavaScript) if no bridge is specified.
|
||||
*/
|
||||
class ConnectivityAction extends ActionAbstract {
|
||||
public function execute() {
|
||||
|
||||
if(!Debug::isEnabled()) {
|
||||
returnError('This action is only available in debug mode!');
|
||||
}
|
||||
|
||||
if(!isset($this->userData['bridge'])) {
|
||||
$this->returnEntryPage();
|
||||
return;
|
||||
}
|
||||
|
||||
$bridgeName = $this->userData['bridge'];
|
||||
|
||||
$this->reportBridgeConnectivity($bridgeName);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a report about the bridge connectivity status and sends it back
|
||||
* to the user.
|
||||
*
|
||||
* The report is generated as Json-formatted string in the format
|
||||
* {
|
||||
* "bridge": "<bridge-name>",
|
||||
* "successful": true/false
|
||||
* }
|
||||
*
|
||||
* @param string $bridgeName Name of the bridge to generate the report for
|
||||
* @return void
|
||||
*/
|
||||
private function reportBridgeConnectivity($bridgeName) {
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
if(!$bridgeFac->isWhitelisted($bridgeName)) {
|
||||
header('Content-Type: text/html');
|
||||
returnServerError('Bridge is not whitelisted!');
|
||||
}
|
||||
|
||||
header('Content-Type: text/json');
|
||||
|
||||
$retVal = array(
|
||||
'bridge' => $bridgeName,
|
||||
'successful' => false,
|
||||
'http_code' => 200,
|
||||
);
|
||||
|
||||
$bridge = $bridgeFac->create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
echo json_encode($retVal);
|
||||
return;
|
||||
}
|
||||
|
||||
$curl_opts = array(
|
||||
CURLOPT_CONNECTTIMEOUT => 5
|
||||
);
|
||||
|
||||
try {
|
||||
$reply = getContents($bridge::URI, array(), $curl_opts, true);
|
||||
|
||||
if($reply) {
|
||||
$retVal['successful'] = true;
|
||||
if (isset($reply['header'])) {
|
||||
if (strpos($reply['header'], 'HTTP/1.1 301 Moved Permanently') !== false) {
|
||||
$retVal['http_code'] = 301;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
$retVal['successful'] = false;
|
||||
}
|
||||
|
||||
echo json_encode($retVal);
|
||||
|
||||
}
|
||||
|
||||
private function returnEntryPage() {
|
||||
echo <<<EOD
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="static/bootstrap.min.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="static/connectivity.css">
|
||||
<script src="static/connectivity.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content" class="container">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<div id="status-message" class="sticky-top alert alert-primary alert-dismissible fade show" role="alert">
|
||||
<i id="status-icon" class="fas fa-sync"></i>
|
||||
<span>...</span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="stopConnectivityChecks()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="search" onkeyup="search()" placeholder="Search for bridge..">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOD;
|
||||
}
|
||||
}
|
53
actions/DetectAction.php
Normal file
53
actions/DetectAction.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 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!');
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
|
||||
|
||||
if(!$bridgeFac->isWhitelisted($bridgeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = $bridgeFac->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);
|
||||
}
|
||||
}
|
260
actions/DisplayAction.php
Normal file
260
actions/DisplayAction.php
Normal file
@@ -0,0 +1,260 @@
|
||||
<?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 {
|
||||
private function get_return_code($error) {
|
||||
$returnCode = $error->getCode();
|
||||
if ($returnCode === 301 || $returnCode === 302) {
|
||||
# Don't pass redirect codes to the exterior
|
||||
$returnCode = 508;
|
||||
}
|
||||
return $returnCode;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
|
||||
|
||||
$format = $this->userData['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
// whitelist control
|
||||
if(!$bridgeFac->isWhitelisted($bridge)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
|
||||
// Data retrieval
|
||||
$bridge = $bridgeFac->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
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope('');
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setKey($cache_params);
|
||||
|
||||
$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->loadConfiguration();
|
||||
$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(),
|
||||
'donationUri' => $bridge->getDonationURI(),
|
||||
'icon' => $bridge->getIcon()
|
||||
);
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
|
||||
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
|
||||
if(Configuration::getConfig('error', 'output') === 'feed') {
|
||||
$item = new \FeedItem();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
// Error 0 is a special case (i.e. "trying to get property of non-object")
|
||||
if($e->getCode() === 0) {
|
||||
$item->setTitle(
|
||||
'Bridge encountered an unexpected situation! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
} else {
|
||||
$item->setTitle(
|
||||
'Bridge returned error '
|
||||
. $e->getCode()
|
||||
. '! ('
|
||||
. $this->userData['_error_time']
|
||||
. ')'
|
||||
);
|
||||
}
|
||||
|
||||
$item->setURI(
|
||||
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
|
||||
. '?'
|
||||
. http_build_query($this->userData)
|
||||
);
|
||||
|
||||
$item->setTimestamp(time());
|
||||
$item->setContent(buildBridgeException($e, $bridge));
|
||||
|
||||
$items[] = $item;
|
||||
} elseif(Configuration::getConfig('error', 'output') === 'http') {
|
||||
header('Content-Type: text/html', true, $this->get_return_code($e));
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
error_log($e);
|
||||
|
||||
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
|
||||
if(Configuration::getConfig('error', 'output') === 'feed') {
|
||||
$item = new \FeedItem();
|
||||
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
$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;
|
||||
} elseif(Configuration::getConfig('error', 'output') === 'http') {
|
||||
header('Content-Type: text/html', true, $this->get_return_code($e));
|
||||
die(buildTransformException($e, $bridge));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store data in cache
|
||||
$cache->saveData(array(
|
||||
'items' => array_map(function($i){ return $i->toArray(); }, $items),
|
||||
'extraInfos' => $infos
|
||||
));
|
||||
|
||||
}
|
||||
|
||||
// Data transformation
|
||||
try {
|
||||
$formatFac = new FormatFactory();
|
||||
$formatFac->setWorkingDir(PATH_LIB_FORMATS);
|
||||
$format = $formatFac->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));
|
||||
}
|
||||
}
|
||||
}
|
57
actions/ListAction.php
Normal file
57
actions/ListAction.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?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;
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
|
||||
|
||||
$bridge = $bridgeFac->create($bridgeName);
|
||||
|
||||
if($bridge === false) { // Broken bridge, show as inactive
|
||||
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => 'inactive'
|
||||
);
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
$status = $bridgeFac->isWhitelisted($bridgeName) ? 'active' : 'inactive';
|
||||
|
||||
$list->bridges[$bridgeName] = array(
|
||||
'status' => $status,
|
||||
'uri' => $bridge->getURI(),
|
||||
'donationUri' => $bridge->getDonationURI(),
|
||||
'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"]
|
||||
}
|
||||
|
45
bridges/ABCNewsBridge.php
Normal file
45
bridges/ABCNewsBridge.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
class ABCNewsBridge extends BridgeAbstract {
|
||||
const NAME = 'ABC News Bridge';
|
||||
const URI = 'https://www.abc.net.au';
|
||||
const DESCRIPTION = 'Topics of the Australian Broadcasting Corporation';
|
||||
const MAINTAINER = 'yue-dongchen';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'topic' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Region',
|
||||
'title' => 'Choose state',
|
||||
'values' => array(
|
||||
'ACT' => 'act',
|
||||
'NSW' => 'nsw',
|
||||
'NT' => 'nt',
|
||||
'QLD' => 'qld',
|
||||
'SA' => 'sa',
|
||||
'TAS' => 'tas',
|
||||
'VIC' => 'vic',
|
||||
'WA' => 'wa'
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$url = 'https://www.abc.net.au/news/' . $this->getInput('topic');
|
||||
$html = getSimpleHTMLDOM($url)->find('.YAJzu._26IxR._2kxNB._3BZxh', 0);
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
foreach($html->find('._2H7Su') as $article) {
|
||||
$item = array();
|
||||
|
||||
$title = $article->find('._3T9Id.fmhNa.nsZdE._2c2Zy._1tOey._3EOTW', 0);
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['uri'] = $title->href;
|
||||
$item['content'] = $article->find('.rMkro._1cBaI._3PhF6._10YQT._1yL-m', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($article->find('time', 0)->datetime);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
|
119
bridges/AO3Bridge.php
Normal file
119
bridges/AO3Bridge.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?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);
|
||||
$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);
|
||||
$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';
|
||||
}
|
||||
}
|
91
bridges/ARDMediathekBridge.php
Normal file
91
bridges/ARDMediathekBridge.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
class ARDMediathekBridge extends BridgeAbstract {
|
||||
const NAME = 'ARD-Mediathek Bridge';
|
||||
const URI = 'https://www.ardmediathek.de';
|
||||
const DESCRIPTION = 'Feed of any series in the ARD-Mediathek, specified by its path';
|
||||
const MAINTAINER = 'yue-dongchen';
|
||||
/*
|
||||
* Number of Items to be requested from ARDmediathek API
|
||||
* 12 has been observed on the wild
|
||||
* 29 is the highest successfully tested value
|
||||
* More Items could be fetched via pagination
|
||||
* The JSON-field pagination holds more information on that
|
||||
* @const PAGESIZE number of requested items
|
||||
*/
|
||||
const PAGESIZE = 29;
|
||||
/*
|
||||
* The URL Prefix of the (Webapp-)API
|
||||
* @const APIENDPOINT https-URL of the used endpoint
|
||||
*/
|
||||
const APIENDPOINT = 'https://api.ardmediathek.de/page-gateway/widgets/ard/asset/';
|
||||
/*
|
||||
* The URL prefix of the video link
|
||||
* URLs from the webapp include a slug containing titles of show, episode, and tv station.
|
||||
* It seems to work without that.
|
||||
* @const VIDEOLINKPREFIX https-URL prefix of video links
|
||||
*/
|
||||
const VIDEOLINKPREFIX = 'https://www.ardmediathek.de/video/';
|
||||
/*
|
||||
* The requested width of the preview image
|
||||
* 432 has been observed on the wild
|
||||
* The webapp seems to also compute and add the height value
|
||||
* It seems to works without that.
|
||||
* @const IMAGEWIDTH width in px of the preview image
|
||||
*/
|
||||
const IMAGEWIDTH = 432;
|
||||
/*
|
||||
* Placeholder that will be replace by IMAGEWIDTH in the preview image URL
|
||||
* @const IMAGEWIDTHPLACEHOLDER
|
||||
*/
|
||||
const IMAGEWIDTHPLACEHOLDER = '{width}';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'path' => array(
|
||||
'name' => 'Show Link or ID',
|
||||
'required' => true,
|
||||
'title' => 'Link to the show page or just its alphanumeric suffix',
|
||||
'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
|
||||
$pathComponents = explode('/', $this->getInput('path'));
|
||||
if (empty($pathComponents)) {
|
||||
returnClientError('Path may not be empty');
|
||||
}
|
||||
if (count($pathComponents) < 2) {
|
||||
$showID = $pathComponents[0];
|
||||
} else {
|
||||
$lastKey = count($pathComponents) - 1;
|
||||
$showID = $pathComponents[$lastKey];
|
||||
if (strlen($showID) === 0) {
|
||||
$showID = $pathComponents[$lastKey - 1];
|
||||
}
|
||||
}
|
||||
|
||||
$url = SELF::APIENDPOINT . $showID . '/?pageSize=' . SELF::PAGESIZE;
|
||||
$rawJSON = getContents($url);
|
||||
$processedJSON = json_decode($rawJSON);
|
||||
|
||||
foreach($processedJSON->teasers as $video) {
|
||||
$item = array();
|
||||
// there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId
|
||||
$item['uri'] = SELF::VIDEOLINKPREFIX . $video->id . '/';
|
||||
// there is also ->mediumTitle and ->shortTitle
|
||||
$item['title'] = $video->longTitle;
|
||||
// in the test, aspect16x9 was the only child of images, not sure whether that is always true
|
||||
$item['enclosures'] = array(
|
||||
str_replace(SELF::IMAGEWIDTHPLACEHOLDER, SELF::IMAGEWIDTH, $video->images->aspect16x9->src)
|
||||
);
|
||||
$item['content'] = '<img src="' . $item['enclosures'][0] . '" /><p>';
|
||||
$item['timestamp'] = $video->broadcastedOn;
|
||||
$item['uid'] = $video->id;
|
||||
$item['author'] = $video->publicationService->name;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
55
bridges/ASRockNewsBridge.php
Normal file
55
bridges/ASRockNewsBridge.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
class ASRockNewsBridge extends BridgeAbstract {
|
||||
const NAME = 'ASRock News Bridge';
|
||||
const URI = 'https://www.asrock.com';
|
||||
const DESCRIPTION = 'Returns latest news articles';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array();
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp');
|
||||
|
||||
$html = defaultLinkTo($html, self::URI . '/news/');
|
||||
|
||||
foreach($html->find('div.inner > a') as $index => $a) {
|
||||
$item = array();
|
||||
|
||||
$articlePath = $a->href;
|
||||
|
||||
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT);
|
||||
|
||||
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
|
||||
|
||||
$contents = $articlePageHtml->find('div.Contents', 0);
|
||||
|
||||
$item['uri'] = $articlePath;
|
||||
$item['title'] = $contents->find('h3', 0)->innertext;
|
||||
|
||||
$contents->find('h3', 0)->outertext = '';
|
||||
|
||||
$item['content'] = $contents->innertext;
|
||||
$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
$item['enclosures'][] = $a->find('img', 0)->src;
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function extractDate($text) {
|
||||
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
|
||||
|
||||
$text = trim($text);
|
||||
|
||||
if (preg_match($dateRegex, $text, $matches)) {
|
||||
return $matches[1];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
@@ -21,5 +21,4 @@ class AcrimedBridge extends FeedExpander {
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
54
bridges/AirBreizhBridge.php
Normal file
54
bridges/AirBreizhBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
class AirBreizhBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'fanch317';
|
||||
const NAME = 'Air Breizh';
|
||||
const URI = 'https://www.airbreizh.asso.fr/';
|
||||
const DESCRIPTION = 'Returns newests publications on Air Breizh';
|
||||
const PARAMETERS = array(
|
||||
'Publications' => array(
|
||||
'theme' => array(
|
||||
'name' => 'Thematique',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Tout' => '',
|
||||
'Rapport d\'activite' => 'rapport-dactivite',
|
||||
'Etude' => 'etudes',
|
||||
'Information' => 'information',
|
||||
'Autres documents' => 'autres-documents',
|
||||
'Plan Régional de Surveillance de la qualité de l’air' => 'prsqa',
|
||||
'Transport' => 'transport'
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme'))
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
foreach ($html->find('article') as $article) {
|
||||
$item = array();
|
||||
// Title
|
||||
$item['title'] = $article->find('h2', 0)->plaintext;
|
||||
// Author
|
||||
$item['author'] = 'Air Breizh';
|
||||
// Image
|
||||
$imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src');
|
||||
// Content preview
|
||||
$item['content'] = '<img src="' . $imagelink . '" />
|
||||
<br/>'
|
||||
. $article->find('.card__text', 0)->plaintext;
|
||||
// URL
|
||||
$item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
|
||||
// ID
|
||||
$item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
72
bridges/AlbionOnlineBridge.php
Normal file
72
bridges/AlbionOnlineBridge.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
class AlbionOnlineBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Albion Online Changelog';
|
||||
const MAINTAINER = 'otakuf';
|
||||
const URI = 'https://albiononline.com';
|
||||
const DESCRIPTION = 'Returns the changes made to the Albion Online';
|
||||
const CACHE_TIMEOUT = 3600; // 60min
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 5,
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Deutsch' => 'de',
|
||||
'Polski' => 'pl',
|
||||
'Français' => 'fr',
|
||||
'Русский' => 'ru',
|
||||
'Português' => 'pt',
|
||||
'Español' => 'es',
|
||||
),
|
||||
'title' => 'Language of changelog posts',
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full changelog',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Enable to receive the full changelog post for each item'
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
$api = 'https://albiononline.com/';
|
||||
// Example: https://albiononline.com/en/changelog/1/5
|
||||
$url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount');
|
||||
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
foreach ($html->find('li') as $data) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href');
|
||||
$item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]);
|
||||
// Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language
|
||||
//print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') );
|
||||
//$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
$item['author'] = 'albiononline.com';
|
||||
if($this->getInput('full')) {
|
||||
$item['content'] = $this->getFullChangelog($item['uri']);
|
||||
} else {
|
||||
//$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext));
|
||||
// Just use title, no info at all or use title and date, see above
|
||||
$item['content'] = $item['title'];
|
||||
}
|
||||
$item['uid'] = hash('sha256', $item['title']);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getFullChangelog($url) {
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
return $html->find('div.small-12.columns', 1)->innertext;
|
||||
}
|
||||
}
|
@@ -8,15 +8,25 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'Bridge for allocine.fr';
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
'name' => 'category',
|
||||
'name' => 'Emission',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'exampleValue' => 'Faux Raccord',
|
||||
'title' => 'Select your category',
|
||||
'title' => 'Sélectionner l\'emission',
|
||||
'values' => array(
|
||||
'Faux Raccord' => 'faux-raccord',
|
||||
'Top 5' => 'top-5',
|
||||
'Tueurs en Séries' => 'tueurs-en-serie'
|
||||
'Fanzone' => 'fanzone',
|
||||
'Game In Ciné' => 'game-in-cine',
|
||||
'Pour la faire courte' => 'pour-la-faire-courte',
|
||||
'Home Cinéma' => 'home-cinema',
|
||||
'PILS - Par Ici Les Sorties' => 'pils-par-ici-les-sorties',
|
||||
'AlloCiné : l\'émission, sur LeStream' => 'allocine-lemission-sur-lestream',
|
||||
'Give Me Five' => 'give-me-five',
|
||||
'Aviez-vous remarqué ?' => 'aviez-vous-remarque',
|
||||
'Et paf, il est mort' => 'et-paf-il-est-mort',
|
||||
'The Big Fan Theory' => 'the-big-fan-theory',
|
||||
'Clichés' => 'cliches',
|
||||
'Complètement...' => 'completement',
|
||||
'#Fun Facts' => 'fun-facts',
|
||||
'Origin Story' => 'origin-story',
|
||||
)
|
||||
)
|
||||
));
|
||||
@@ -24,19 +34,30 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
|
||||
switch($this->getInput('category')) {
|
||||
case 'faux-raccord':
|
||||
$uri = static::URI . 'video/programme-12284/saison-32180/';
|
||||
break;
|
||||
case 'top-5':
|
||||
$uri = static::URI . 'video/programme-12299/saison-29561/';
|
||||
break;
|
||||
case 'tueurs-en-serie':
|
||||
$uri = static::URI . 'video/programme-12286/saison-22938/';
|
||||
break;
|
||||
}
|
||||
$categories = array(
|
||||
'faux-raccord' => 'video/programme-12284/saison-37054/',
|
||||
'fanzone' => 'video/programme-12298/saison-37059/',
|
||||
'game-in-cine' => 'video/programme-12288/saison-22971/',
|
||||
'pour-la-faire-courte' => 'video/programme-20960/saison-29678/',
|
||||
'home-cinema' => 'video/programme-12287/saison-34703/',
|
||||
'pils-par-ici-les-sorties' => 'video/programme-25789/saison-37253/',
|
||||
'allocine-lemission-sur-lestream' => 'video/programme-25123/saison-36067/',
|
||||
'give-me-five' => 'video/programme-21919/saison-34518/',
|
||||
'aviez-vous-remarque' => 'video/programme-19518/saison-37084/',
|
||||
'et-paf-il-est-mort' => 'video/programme-25113/saison-36657/',
|
||||
'the-big-fan-theory' => 'video/programme-20403/saison-37419/',
|
||||
'cliches' => 'video/programme-24834/saison-35591/',
|
||||
'completement' => 'video/programme-23859/saison-34102/',
|
||||
'fun-facts' => 'video/programme-23040/saison-32686/',
|
||||
'origin-story' => 'video/programme-25667/saison-37041/'
|
||||
);
|
||||
|
||||
return $uri;
|
||||
$category = $this->getInput('category');
|
||||
if(array_key_exists($category, $categories)) {
|
||||
return static::URI . $categories[$category];
|
||||
} else {
|
||||
returnClientError('Emission inconnue');
|
||||
}
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
@@ -45,7 +66,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']
|
||||
);
|
||||
@@ -56,32 +77,30 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request ' . $this->getURI() . ' !');
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$category = array_search(
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
||||
foreach($html->find('.media-meta-list figure.media-meta-fig') as $element) {
|
||||
foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('div.titlebar h3.title a', 0);
|
||||
$content = trim($element->innertext);
|
||||
$figCaption = strpos($content, $category);
|
||||
$title = $element->find('a[class*=meta-title-link]', 0);
|
||||
$content = trim($element->outertext);
|
||||
|
||||
if($figCaption !== false) {
|
||||
$content = str_replace('src="/', 'src="' . static::URI, $content);
|
||||
$content = str_replace('href="/', 'href="' . static::URI, $content);
|
||||
$content = str_replace('src=\'/', 'src=\'' . static::URI, $content);
|
||||
$content = str_replace('href=\'/', 'href=\'' . static::URI, $content);
|
||||
$item['content'] = $content;
|
||||
$item['title'] = trim($title->innertext);
|
||||
$item['uri'] = static::URI . $title->href;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
// Replace image 'src' with the one in 'data-src'
|
||||
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content);
|
||||
$content = preg_replace('@data-src=@', 'src=', $content);
|
||||
|
||||
// Remove date in the content to prevent content update while the video is getting older
|
||||
$content = preg_replace('@<div class="meta-sub light">.*<span>[^<]*</span>[^<]*</div>@', '', $content);
|
||||
|
||||
$item['content'] = $content;
|
||||
$item['title'] = trim($title->innertext);
|
||||
$item['uri'] = static::URI . substr($title->href, 1);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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,11 +58,10 @@ 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.');
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
foreach($html->find('li.s-result-item') as $element) {
|
||||
|
||||
@@ -72,6 +69,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 +86,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;
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
|
||||
class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const MAINTAINER = 'captn3m0, sal0max';
|
||||
const NAME = 'Amazon Price Tracker';
|
||||
const URI = 'https://www.amazon.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
@@ -19,7 +19,6 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
'tld' => array(
|
||||
'name' => 'Country',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'values' => array(
|
||||
'Australia' => 'com.au',
|
||||
'Brazil' => 'com.br',
|
||||
@@ -33,6 +32,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
'Mexico' => 'com.mx',
|
||||
'Netherlands' => 'nl',
|
||||
'Spain' => 'es',
|
||||
'Sweden' => 'se',
|
||||
'United Kingdom' => 'co.uk',
|
||||
'United States' => 'com',
|
||||
),
|
||||
@@ -40,6 +40,15 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
),
|
||||
));
|
||||
|
||||
const PRICE_SELECTORS = array(
|
||||
'#priceblock_ourprice',
|
||||
'.priceBlockBuyingPriceString',
|
||||
'#newBuyBoxPrice',
|
||||
'#tp_price_block_total_price_ww',
|
||||
'span.offer-price',
|
||||
'.a-color-price',
|
||||
);
|
||||
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
@@ -54,7 +63,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
*/
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('asin'))) {
|
||||
return $this->getDomainName() . '/dp/' . $this->getInput('asin') . '/';
|
||||
return $this->getDomainName() . '/dp/' . $this->getInput('asin');
|
||||
}
|
||||
return parent::getURI();
|
||||
}
|
||||
@@ -135,27 +144,43 @@ EOT;
|
||||
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
|
||||
// data-asin-currency-code="USD" data-substitute-count="-1" ... />
|
||||
if ($asinData) {
|
||||
return [
|
||||
return array(
|
||||
'price' => $asinData->getAttribute('data-asin-price'),
|
||||
'currency' => $asinData->getAttribute('data-asin-currency-code'),
|
||||
'shipping' => $asinData->getAttribute('data-asin-shipping')
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function scrapePriceGeneric($html) {
|
||||
$priceDiv = $html->find('span.offer-price', 0) ?: $html->find('.a-color-price', 0);
|
||||
$priceDiv = null;
|
||||
|
||||
preg_match('/^\s*([A-Z]{3}|£|\$)\s?([\d.,]+)\s*$/', $priceDiv->plaintext, $matches);
|
||||
foreach(self::PRICE_SELECTORS as $sel) {
|
||||
$priceDiv = $html->find($sel, 0);
|
||||
if ($priceDiv) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (count($matches) === 3) {
|
||||
return [
|
||||
'price' => $matches[2],
|
||||
'currency' => $matches[1],
|
||||
if (!$priceDiv) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$priceString = $priceDiv->plaintext;
|
||||
|
||||
preg_match('/[\d.,]+/', $priceString, $matches);
|
||||
|
||||
$price = $matches[0];
|
||||
$currency = trim(str_replace($price, '', $priceString), " \t\n\r\0\x0B\xC2\xA0");
|
||||
|
||||
if ($price != null && $currency != null) {
|
||||
return array(
|
||||
'price' => $price,
|
||||
'currency' => $currency,
|
||||
'shipping' => '0'
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -176,6 +201,8 @@ EOT;
|
||||
'title' => $this->title,
|
||||
'uri' => $this->getURI(),
|
||||
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
|
||||
// This is to ensure that feed readers notice the price change
|
||||
'uid' => md5($data['price'])
|
||||
);
|
||||
|
||||
if ($data['shipping'] !== '0') {
|
||||
|
217
bridges/AnidexBridge.php
Normal file
217
bridges/AnidexBridge.php
Normal file
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
class AnidexBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Anidex';
|
||||
const URI = 'http://anidex.info/'; // anidex.info has ddos-guard so we need to use anidex.moe
|
||||
const ALTERNATE_URI = 'https://anidex.moe/'; // anidex.moe returns 301 unless Host is set to anidex.info
|
||||
const ALTERNATE_HOST = 'anidex.info'; // Correct host for requesting anidex.moe without 301 redirect
|
||||
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'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::ALTERNATE_URI . '?s=upload_timestamp&o=desc';
|
||||
foreach (array('id', 'lang_id', 'group_id') as $param_name) {
|
||||
$param = $this->getInput($param_name);
|
||||
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
|
||||
$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;
|
||||
}
|
||||
|
||||
// We need to use a different Host HTTP header to reach the correct page on ALTERNATE_URI
|
||||
$headers = array('Host: ' . self::ALTERNATE_HOST);
|
||||
|
||||
// The HTTPS certificate presented by anidex.moe is for anidex.info. We need to ignore this.
|
||||
// As a consequence, the bridge is intentionally marked as insecure by setting self::URI to http://
|
||||
$opt[CURLOPT_SSL_VERIFYHOST] = 0;
|
||||
$opt[CURLOPT_SSL_VERIFYPEER] = 0;
|
||||
|
||||
// Retrieve torrent listing from search results, which does not contain torrent description
|
||||
$html = getSimpleHTMLDOM($search_url, $headers, $opt);
|
||||
$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_browse_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
$item_fetch_uri = self::ALTERNATE_URI . 'torrent/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page (cached for 24 hours: 86400 seconds)
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_fetch_uri, 86400, $headers, $opt)) {
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
|
||||
$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_browse_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',
|
||||
@@ -39,8 +39,7 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
//Retrive page contents
|
||||
$url = self::URI . 'history-0-1/' . $requestFilter;
|
||||
$html = getSimpleHTMLDOM($url)
|
||||
or returnServerError('Could not request Anime-Ultime: ' . $url);
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
//Relases are sorted by day : process each day individually
|
||||
foreach($html->find('div.history', 0)->find('h3') as $daySection) {
|
||||
@@ -65,6 +64,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,10 +85,8 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
if(!empty($item_uri)) {
|
||||
|
||||
// Retrieve description from description page and
|
||||
// convert relative image src info absolute image src
|
||||
$html_item = getContents($item_uri)
|
||||
or returnServerError('Could not request Anime-Ultime: ' . $item_uri);
|
||||
// Retrieve description from description page
|
||||
$html_item = getContents($item_uri);
|
||||
$item_description = substr(
|
||||
$html_item,
|
||||
strpos($html_item, 'class="principal_contain" align="center">') + 41
|
||||
@@ -91,13 +95,11 @@ 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);
|
||||
|
||||
//Build and add final item
|
||||
$item = array();
|
||||
@@ -105,6 +107,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 +134,4 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
150
bridges/AppleAppStoreBridge.php
Normal file
150
bridges/AppleAppStoreBridge.php
Normal file
@@ -0,0 +1,150 @@
|
||||
<?php
|
||||
|
||||
class AppleAppStoreBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const NAME = 'Apple App Store';
|
||||
const URI = 'https://apps.apple.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns version updates for a specific application';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
'id' => array(
|
||||
'name' => 'Application ID',
|
||||
'required' => true,
|
||||
'exampleValue' => '310633997'
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Platform',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'iPad' => 'ipad',
|
||||
'iPhone' => 'iphone',
|
||||
'Mac' => 'mac',
|
||||
|
||||
// The following 2 are present in responses
|
||||
// but not yet tested
|
||||
'Web' => 'web',
|
||||
'Apple TV' => 'appletv',
|
||||
),
|
||||
'defaultValue' => 'iphone',
|
||||
),
|
||||
'country' => array(
|
||||
'name' => 'Store Country',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'US' => 'US',
|
||||
'India' => 'IN',
|
||||
'Canada' => 'CA',
|
||||
'Germany' => 'DE',
|
||||
),
|
||||
'defaultValue' => 'US',
|
||||
),
|
||||
));
|
||||
|
||||
const PLATFORM_MAPPING = array(
|
||||
'iphone' => 'ios',
|
||||
'ipad' => 'ios',
|
||||
);
|
||||
|
||||
private function makeHtmlUrl($id, $country){
|
||||
return 'https://apps.apple.com/' . $country . '/app/id' . $id;
|
||||
}
|
||||
|
||||
private function makeJsonUrl($id, $platform, $country){
|
||||
return "https://amp-api.apps.apple.com/v1/catalog/$country/apps/$id?platform=$platform&extend=versionHistory";
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if (isset($this->name)) {
|
||||
return $this->name . ' - AppStore Updates';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* In case of some platforms, the data is present in the initial response
|
||||
*/
|
||||
private function getDataFromShoebox($id, $platform, $country){
|
||||
$uri = $this->makeHtmlUrl($id, $country);
|
||||
$html = getSimpleHTMLDOMCached($uri, 3600);
|
||||
$script = $html->find('script[id="shoebox-ember-data-store"]', 0);
|
||||
|
||||
$json = json_decode($script->innertext, true);
|
||||
return $json['data'];
|
||||
}
|
||||
|
||||
private function getJWTToken($id, $platform, $country){
|
||||
$uri = $this->makeHtmlUrl($id, $country);
|
||||
|
||||
$html = getSimpleHTMLDOMCached($uri, 3600);
|
||||
|
||||
$meta = $html->find('meta[name="web-experience-app/config/environment"]', 0);
|
||||
|
||||
$json = urldecode($meta->content);
|
||||
|
||||
$json = json_decode($json);
|
||||
|
||||
return $json->MEDIA_API->token;
|
||||
}
|
||||
|
||||
private function getAppData($id, $platform, $country, $token){
|
||||
$uri = $this->makeJsonUrl($id, $platform, $country);
|
||||
|
||||
$headers = array(
|
||||
"Authorization: Bearer $token",
|
||||
);
|
||||
|
||||
$json = json_decode(getContents($uri, $headers), true);
|
||||
|
||||
return $json['data'][0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the version history from the data received
|
||||
* @return array list of versions with details on each element
|
||||
*/
|
||||
private function getVersionHistory($data, $platform){
|
||||
switch($platform) {
|
||||
case 'mac':
|
||||
return $data['relationships']['platforms']['data'][0]['attributes']['versionHistory'];
|
||||
default:
|
||||
$os = self::PLATFORM_MAPPING[$platform];
|
||||
return $data['attributes']['platformAttributes'][$os]['versionHistory'];
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$id = $this->getInput('id');
|
||||
$country = $this->getInput('country');
|
||||
$platform = $this->getInput('p');
|
||||
|
||||
switch ($platform) {
|
||||
case 'mac':
|
||||
$data = $this->getDataFromShoebox($id, $platform, $country);
|
||||
break;
|
||||
|
||||
default:
|
||||
$token = $this->getJWTToken($id, $platform, $country);
|
||||
$data = $this->getAppData($id, $platform, $country, $token);
|
||||
}
|
||||
|
||||
$versionHistory = $this->getVersionHistory($data, $platform);
|
||||
$name = $this->name = $data['attributes']['name'];
|
||||
$author = $data['attributes']['artistName'];
|
||||
|
||||
foreach ($versionHistory as $row) {
|
||||
$item = array();
|
||||
|
||||
$item['content'] = nl2br($row['releaseNotes']);
|
||||
$item['title'] = $name . ' - ' . $row['versionDisplay'];
|
||||
$item['timestamp'] = $row['releaseDate'];
|
||||
$item['author'] = $author;
|
||||
|
||||
$item['uri'] = $this->makeHtmlUrl($id, $country);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
55
bridges/AppleMusicBridge.php
Normal file
55
bridges/AppleMusicBridge.php
Normal file
@@ -0,0 +1,55 @@
|
||||
<?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 = 'bockiii';
|
||||
const PARAMETERS = array(array(
|
||||
'artist' => array(
|
||||
'name' => 'Artist ID',
|
||||
'exampleValue' => '909253',
|
||||
'required' => true,
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Latest X Releases (max 50)',
|
||||
'defaultValue' => '10',
|
||||
'required' => true,
|
||||
),
|
||||
));
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
public function collectData() {
|
||||
# Limit the amount of releases to 50
|
||||
if ($this->getInput('limit') > 50) {
|
||||
$limit = 50;
|
||||
} else {
|
||||
$limit = $this->getInput('limit');
|
||||
}
|
||||
|
||||
$url = 'https://itunes.apple.com/lookup?id='
|
||||
. $this->getInput('artist')
|
||||
. '&entity=album&limit='
|
||||
. $limit .
|
||||
'&sort=recent';
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
$json = json_decode($html);
|
||||
|
||||
foreach ($json->results as $obj) {
|
||||
if ($obj->wrapperType === 'collection') {
|
||||
$this->items[] = array(
|
||||
'title' => $obj->artistName . ' - ' . $obj->collectionName,
|
||||
'uri' => $obj->collectionViewUrl,
|
||||
'timestamp' => $obj->releaseDate,
|
||||
'enclosures' => $obj->artworkUrl100,
|
||||
'content' => '<a href=' . $obj->collectionViewUrl
|
||||
. '><img src="' . $obj->artworkUrl100 . '" /></a><br><br>'
|
||||
. $obj->artistName . ' - ' . $obj->collectionName
|
||||
. '<br>'
|
||||
. $obj->copyright,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
91
bridges/ArtStationBridge.php
Normal file
91
bridges/ArtStationBridge.php
Normal file
@@ -0,0 +1,91 @@
|
||||
<?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);
|
||||
return json_decode($jsonSearchStr);
|
||||
}
|
||||
|
||||
private function fetchProject($hashID) {
|
||||
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
|
||||
$jsonProjectStr = getContents($jsonProjectURL);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class Arte7Bridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
// const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Arte +7';
|
||||
const URI = 'https://www.arte.tv/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
@@ -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,21 +68,30 @@ 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 : '');
|
||||
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
);
|
||||
|
||||
$input = getContents($url, $header) or die('Could not request ARTE.');
|
||||
$input = getContents($url, $header);
|
||||
$input_json = json_decode($input, true);
|
||||
|
||||
foreach($input_json['videos'] as $element) {
|
||||
@@ -96,5 +119,4 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
71
bridges/AsahiShimbunAJWBridge.php
Normal file
71
bridges/AsahiShimbunAJWBridge.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?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')));
|
||||
|
||||
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
|
||||
@@ -16,42 +16,41 @@ class AskfmBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Requested username can\'t be found.');
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
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 +65,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();
|
||||
|
4637
bridges/AtmoNouvelleAquitaineBridge.php
Normal file
4637
bridges/AtmoNouvelleAquitaineBridge.php
Normal file
File diff suppressed because it is too large
Load Diff
57
bridges/AtmoOccitanieBridge.php
Normal file
57
bridges/AtmoOccitanieBridge.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
class AtmoOccitanieBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Atmo Occitanie';
|
||||
const URI = 'https://www.atmo-occitanie.org/';
|
||||
const DESCRIPTION = 'Fetches the latest air polution of cities in Occitanie from Atmo';
|
||||
const MAINTAINER = 'floviolleau';
|
||||
const PARAMETERS = array(array(
|
||||
'city' => array(
|
||||
'name' => 'Ville',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 7200;
|
||||
|
||||
public function collectData() {
|
||||
$uri = self::URI . $this->getInput('city');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
$generalMessage = $html->find('.landing-ville .city-banner .iqa-avertissement', 0)->innertext;
|
||||
$recommendationsDom = $html->find('.landing-ville .recommandations', 0);
|
||||
$recommendationsItemDom = $recommendationsDom->find('.recommandation-item .label');
|
||||
|
||||
$recommendationsMessage = '';
|
||||
|
||||
$i = 0;
|
||||
$len = count($recommendationsItemDom);
|
||||
foreach ($recommendationsItemDom as $key => $value) {
|
||||
if ($i == 0) {
|
||||
$recommendationsMessage .= trim($value->innertext) . '.';
|
||||
} else {
|
||||
$recommendationsMessage .= ' ' . trim($value->innertext) . '.';
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
|
||||
$lastRecommendationsDom = $recommendationsDom->find('.col-md-6', -1);
|
||||
$informationHeaderMessage = $lastRecommendationsDom->find('.heading', 0)->innertext;
|
||||
$indice = $lastRecommendationsDom->find('.current-indice .indice div', 0)->innertext;
|
||||
$informationDescriptionMessage = $lastRecommendationsDom->find('.current-indice .description p', 0)->innertext;
|
||||
|
||||
$message = "$generalMessage L'indice est de $indice/10. $informationDescriptionMessage. $recommendationsMessage";
|
||||
$city = $this->getInput('city');
|
||||
|
||||
$item['uri'] = $uri;
|
||||
$today = date('d/m/Y');
|
||||
$item['title'] = "Bulletin de l'air du $today pour la ville : $city.";
|
||||
//$item['title'] .= ' Retrouvez plus d\'informations en allant sur atmo-occitanie.org #QualiteAir. ' . $message;
|
||||
$item['title'] .= ' #QualiteAir. ' . $message;
|
||||
$item['author'] = 'floviolleau';
|
||||
$item['content'] = $message;
|
||||
$item['uid'] = hash('sha256', $item['title']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
135
bridges/AutoJMBridge.php
Normal file
135
bridges/AutoJMBridge.php
Normal file
@@ -0,0 +1,135 @@
|
||||
<?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 sur la recheche AutoJM' => array(
|
||||
'url' => array(
|
||||
'name' => 'URL de la page de recherche',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
|
||||
'exampleValue' => 'recherche?brands[]=peugeot&ranges[]=peugeot-nouvelle-308-2021-5p'
|
||||
),
|
||||
)
|
||||
);
|
||||
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 sur la recheche AutoJM':
|
||||
return 'AutoJM | Recherche de véhicules';
|
||||
break;
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
// Get the number of result for this search
|
||||
$search_url = self::URI . $this->getInput('url') . '&open=energy&onlyFilters=false';
|
||||
|
||||
// Set the header 'X-Requested-With' like the website does it
|
||||
$header = array(
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
);
|
||||
|
||||
// Get the JSON content of the form
|
||||
$json = getContents($search_url, $header);
|
||||
|
||||
// Extract the HTML content from the JSON result
|
||||
$data = json_decode($json);
|
||||
|
||||
$nb_results = $data->nbResults;
|
||||
$total_pages = ceil($nb_results / 15);
|
||||
|
||||
// Limit the number of page to analyse to 10
|
||||
for($page = 1; $page <= $total_pages && $page <= 10; $page++) {
|
||||
// Get the result the next page
|
||||
$html = $this->getResults($page);
|
||||
|
||||
// Go through every car of the search
|
||||
$list = $html->find('div[class*=card-car card-car--listing]');
|
||||
foreach ($list as $car) {
|
||||
|
||||
// Get the info about the car offer
|
||||
$image = $car->find('div[class=card-car__header__img]', 0)->find('img', 0)->src;
|
||||
// Decode HTML attribute JSON data
|
||||
$car_data = json_decode(html_entity_decode($car->{'data-layer'}));
|
||||
$car_model = $car->{'data-title'} . ' ' . $car->{'data-suptitle'};
|
||||
$availability = $car->find('div[class=card-car__modalites]', 0)->find('div[class=col]', 0)->plaintext;
|
||||
$warranty = $car->find('div[data-type=WarrantyCard]', 0)->plaintext;
|
||||
$discount_html = $car->find('div[class=subtext vehicle_reference_element]', 0);
|
||||
// Check if there is any discount info displayed
|
||||
if ($discount_html != null) {
|
||||
$reference_price_value = $discount_html->find('span[data-cfg=vehicle__reference_price]', 0)->plaintext;
|
||||
$discount_percent_value = $discount_html->find('span[data-cfg=vehicle__discount_percent]', 0)->plaintext;
|
||||
$reference_price = '<li>Prix de référence : <s>' . $reference_price_value . '</s></li>';
|
||||
$discount_percent = '<li>Réduction : ' . $discount_percent_value . ' %</li>';
|
||||
} else {
|
||||
$reference_price = '';
|
||||
$discount_percent = '';
|
||||
}
|
||||
$price = $car_data->price;
|
||||
$kilometer = $car->find('span[data-cfg=vehicle__kilometer]', 0)->plaintext;
|
||||
$energy = $car->find('span[data-cfg=vehicle__energy__label]', 0)->plaintext;
|
||||
$power = $car->find('span[data-cfg=vehicle__tax_horse_power]', 0)->plaintext;
|
||||
$seats = $car->find('span[data-cfg=vehicle__seats]', 0)->plaintext;
|
||||
$doors = $car->find('span[data-cfg=vehicle__door__label]', 0)->plaintext;
|
||||
$transmission = $car->find('span[data-cfg=vehicle__transmission]', 0)->plaintext;
|
||||
$loa_html = $car->find('span[data-cfg=vehicle__loa]', 0);
|
||||
// Check if any LOA price is displayed
|
||||
if($loa_html != null) {
|
||||
$loa_value = $car->find('span[data-cfg=vehicle__loa]', 0)->plaintext;
|
||||
$loa = '<li>LOA : à partir de ' . $loa_value . ' / mois </li>';
|
||||
} else {
|
||||
$loa = '';
|
||||
}
|
||||
|
||||
// Construct the new item
|
||||
$item = array();
|
||||
$item['title'] = $car_model;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
|
||||
. $car_model . '</p>';
|
||||
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
|
||||
$item['content'] .= '<li>Prix : ' . $price . ' €</li>';
|
||||
$item['content'] .= $reference_price;
|
||||
$item['content'] .= $loa;
|
||||
$item['content'] .= $discount_percent;
|
||||
$item['content'] .= '<li>Garantie : ' . $warranty . '</li>';
|
||||
$item['content'] .= '<li>Kilométrage : ' . $kilometer . ' km</li>';
|
||||
$item['content'] .= '<li>Energie : ' . $energy . '</li>';
|
||||
$item['content'] .= '<li>Puissance: ' . $power . ' CV Fiscaux</li>';
|
||||
$item['content'] .= '<li>Nombre de Places : ' . $seats . ' place(s)</li>';
|
||||
$item['content'] .= '<li>Nombre de portes : ' . $doors . '</li>';
|
||||
$item['content'] .= '<li>Boite de vitesse : ' . $transmission . '</li></ul>';
|
||||
$item['uri'] = $car_data->{'uri'};
|
||||
$item['uid'] = hash('md5', $item['content']);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getResults(int $page)
|
||||
{
|
||||
$user_input = $this->getInput('url');
|
||||
$search_data = preg_replace('#(recherche|recherche/[0-9]{1,10})\?#', 'recherche/' . $page . '?', $user_input);
|
||||
|
||||
$search_url = self::URI . $search_data . '&open=energy&onlyFilters=false';
|
||||
|
||||
// Get the HTML content of the page
|
||||
$html = getSimpleHTMLDOMCached($search_url);
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
54
bridges/AwwwardsBridge.php
Normal file
54
bridges/AwwwardsBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
class AwwwardsBridge extends BridgeAbstract {
|
||||
const NAME = 'Awwwards';
|
||||
const URI = 'https://www.awwwards.com/';
|
||||
const DESCRIPTION = 'Fetches the latest ten sites of the day from Awwwards';
|
||||
const MAINTAINER = 'Paroleen';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const SITESURI = 'https://www.awwwards.com/websites/sites_of_the_day/';
|
||||
const SITEURI = 'https://www.awwwards.com/sites/';
|
||||
const ASSETSURI = 'https://assets.awwwards.com/awards/media/cache/thumb_417_299/';
|
||||
|
||||
private $sites = array();
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.awwwards.com/favicon.ico';
|
||||
}
|
||||
|
||||
private function fetchSites() {
|
||||
Debug::log('Fetching all sites');
|
||||
$sites = getSimpleHTMLDOM(self::SITESURI);
|
||||
|
||||
Debug::log('Parsing all JSON data');
|
||||
foreach($sites->find('li[data-model]') as $site) {
|
||||
$decode = html_entity_decode($site->attr['data-model'],
|
||||
ENT_QUOTES, 'utf-8');
|
||||
$decode = json_decode($decode, true);
|
||||
$this->sites[] = $decode;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->fetchSites();
|
||||
|
||||
Debug::log('Building RSS feed');
|
||||
foreach($this->sites as $site) {
|
||||
$item = array();
|
||||
$item['title'] = $site['title'];
|
||||
$item['timestamp'] = $site['createdAt'];
|
||||
$item['categories'] = $site['tags'];
|
||||
|
||||
$item['content'] = '<img src="'
|
||||
. self::ASSETSURI
|
||||
. $site['images']['thumbnail']
|
||||
. '">';
|
||||
$item['uri'] = self::SITEURI . $site['slug'];
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if(count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
263
bridges/BAEBridge.php
Normal file
263
bridges/BAEBridge.php
Normal file
@@ -0,0 +1,263 @@
|
||||
<?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;
|
||||
$item['content'] = defaultLinkTo($content, parent::getURI());
|
||||
$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;
|
||||
}
|
||||
}
|
432
bridges/BadDragonBridge.php
Normal file
432
bridges/BadDragonBridge.php
Normal file
@@ -0,0 +1,432 @@
|
||||
<?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'));
|
||||
|
||||
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)));
|
||||
|
||||
$productList = json_decode(getContents(self::URI
|
||||
. 'api/inventory-toy/product-list'));
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
186
bridges/BakaUpdatesMangaReleasesBridge.php
Normal file
186
bridges/BakaUpdatesMangaReleasesBridge.php
Normal file
@@ -0,0 +1,186 @@
|
||||
<?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, KamaleiZestri';
|
||||
const PARAMETERS = array(
|
||||
'By series' => array(
|
||||
'series_id' => array(
|
||||
'name' => 'Series ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '12345'
|
||||
)
|
||||
),
|
||||
'By list' => array(
|
||||
'list_id' => array(
|
||||
'name' => 'List ID and Type',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '123456&list=read'
|
||||
)
|
||||
)
|
||||
);
|
||||
const LIMIT_COLS = 5;
|
||||
const LIMIT_ITEMS = 10;
|
||||
const RELEASES_URL = 'https://www.mangaupdates.com/releases.html';
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function collectData() {
|
||||
if($this -> queriedContext == 'By series')
|
||||
$this -> collectDataBySeries();
|
||||
else //queriedContext == 'By list'
|
||||
$this -> collectDataByList();
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if($this -> queriedContext == 'By series') {
|
||||
$series_id = $this->getInput('series_id');
|
||||
if (!empty($series_id)) {
|
||||
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
|
||||
}
|
||||
} else //queriedContext == 'By list'
|
||||
return self::RELEASES_URL;
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!empty($this->feedName)) {
|
||||
return $this->feedName . ' - ' . self::NAME;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getSanitizedHash($string) {
|
||||
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
|
||||
}
|
||||
|
||||
private function filterText($text) {
|
||||
return rtrim($text, '* ');
|
||||
}
|
||||
|
||||
private function filterHTML($text) {
|
||||
return $this->filterText(html_entity_decode($text));
|
||||
}
|
||||
|
||||
private function findID($manga) {
|
||||
// sometimes new series are on the release list that have no ID. just drop them.
|
||||
if(@$this -> filterHTML($manga -> find('a', 0) -> href) != null) {
|
||||
preg_match('/id=([0-9]*)/', $this -> filterHTML($manga -> find('a', 0) -> href), $match);
|
||||
return $match[1];
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
|
||||
private function collectDataBySeries() {
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
// content is an unstructured pile of divs, ugly to parse
|
||||
$cols = $html->find('div#main_content div.row > div.text');
|
||||
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'] . $item['author']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectDataByList() {
|
||||
$this -> feedName = 'Releases';
|
||||
$list = array();
|
||||
|
||||
$releasesHTML = getSimpleHTMLDOM(self::RELEASES_URL);
|
||||
|
||||
$list_id = $this -> getInput('list_id');
|
||||
$listHTML = getSimpleHTMLDOM('https://www.mangaupdates.com/mylist.html?id=' . $list_id);
|
||||
|
||||
//get ids of the manga that the user follows,
|
||||
$parts = $listHTML -> find('table#ptable tr > td.pl');
|
||||
foreach($parts as $part) {
|
||||
$list[] = $this -> findID($part);
|
||||
}
|
||||
|
||||
//similar to above, but the divs are in groups of 3.
|
||||
$cols = $releasesHTML -> find('div#main_content div.row > div.pbreak');
|
||||
$rows = array_slice(array_chunk($cols, 3), 0);
|
||||
|
||||
foreach($rows as $cols) {
|
||||
//check if current manga is in user's list.
|
||||
$id = $this -> findId($cols[0]);
|
||||
if(!array_search($id, $list)) continue;
|
||||
|
||||
$item = array();
|
||||
$title = array();
|
||||
|
||||
$item['content'] = '';
|
||||
|
||||
$objTitle = $cols[0];
|
||||
if ($objTitle) {
|
||||
$title[] = $this->filterHTML($objTitle->plaintext);
|
||||
$item['content'] .= '<p>Series: ' . $this->filterHTML($objTitle -> innertext) . '</p>';
|
||||
}
|
||||
|
||||
$objVolChap = $cols[1];
|
||||
if ($objVolChap && !empty($objVolChap->plaintext))
|
||||
$title[] = $this -> filterHTML($objVolChap -> innertext);
|
||||
|
||||
$objAuthor = $cols[2];
|
||||
if ($objAuthor && !empty($objAuthor->plaintext)) {
|
||||
$item['author'] = $this->filterHTML($objAuthor -> plaintext);
|
||||
$item['content'] .= '<p>Groups: ' . $this->filterHTML($objAuthor -> innertext) . '</p>';
|
||||
}
|
||||
|
||||
$item['title'] = implode(' ', $title);
|
||||
$item['uri'] = self::URI . 'releases.html?search=' . $id . '&stype=series';
|
||||
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,63 +1,403 @@
|
||||
<?php
|
||||
class BandcampBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'sebsauvage';
|
||||
const NAME = 'Bandcamp Tag';
|
||||
const MAINTAINER = 'sebsauvage, Roliga';
|
||||
const NAME = 'Bandcamp Bridge';
|
||||
const URI = 'https://bandcamp.com/';
|
||||
const CACHE_TIMEOUT = 600; // 10min
|
||||
const DESCRIPTION = 'New bandcamp release by tag';
|
||||
const PARAMETERS = array( array(
|
||||
'tag' => array(
|
||||
'name' => 'tag',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
const DESCRIPTION = 'New bandcamp releases by tag, band or album';
|
||||
const PARAMETERS = array(
|
||||
'By tag' => array(
|
||||
'tag' => array(
|
||||
'name' => 'tag',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
)
|
||||
),
|
||||
'By band' => array(
|
||||
'band' => array(
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the band page URL',
|
||||
'required' => true
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
),
|
||||
'defaultValue' => 'changes'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Number of releases to return',
|
||||
'defaultValue' => 5
|
||||
)
|
||||
),
|
||||
'By label' => array(
|
||||
'label' => array(
|
||||
'name' => 'label',
|
||||
'type' => 'text',
|
||||
'title' => 'label name as seen in the label page URL',
|
||||
'required' => true
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
),
|
||||
'defaultValue' => 'changes'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Number of releases to return',
|
||||
'defaultValue' => 5
|
||||
)
|
||||
),
|
||||
'By album' => array(
|
||||
'band' => array(
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the album page URL',
|
||||
'required' => true
|
||||
),
|
||||
'album' => array(
|
||||
'name' => 'album',
|
||||
'type' => 'text',
|
||||
'title' => 'Album name as seen in the album page URL',
|
||||
'required' => true
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
),
|
||||
'defaultValue' => 'tracks'
|
||||
)
|
||||
)
|
||||
));
|
||||
);
|
||||
const IMGURI = 'https://f4.bcbits.com/';
|
||||
const IMGSIZE_300PX = 23;
|
||||
const IMGSIZE_700PX = 16;
|
||||
|
||||
private $feedName;
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://s4.bcbits.com/img/bc_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('No results for this query.');
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
$url = self::URI . 'api/hub/1/dig_deeper';
|
||||
$data = $this->buildRequestJson();
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data)
|
||||
);
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
);
|
||||
$content = getContents($url, $header, $opts);
|
||||
|
||||
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');
|
||||
$this->items[] = $item;
|
||||
$item = array(
|
||||
'uri' => $url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title
|
||||
);
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title";
|
||||
$item['enclosures'] = array($img);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
case 'By band':
|
||||
case 'By label':
|
||||
case 'By album':
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
|
||||
|
||||
if ($html->find('meta[name=title]', 0)) {
|
||||
$this->feedName = $html->find('meta[name=title]', 0)->content;
|
||||
} else {
|
||||
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
|
||||
}
|
||||
|
||||
$regex = '/band_id=(\d+)/';
|
||||
if(preg_match($regex, $html, $matches) == false)
|
||||
returnServerError('Unable to find band ID on: ' . $this->getURI());
|
||||
$band_id = $matches[1];
|
||||
|
||||
$tralbums = array();
|
||||
switch($this->queriedContext) {
|
||||
case 'By band':
|
||||
case 'By label':
|
||||
$query_data = array(
|
||||
'band_id' => $band_id
|
||||
);
|
||||
$band_data = $this->apiGet('mobile/22/band_details', $query_data);
|
||||
|
||||
$num_albums = min(count($band_data->discography), $this->getInput('limit'));
|
||||
for($i = 0; $i < $num_albums; $i++) {
|
||||
$album_basic_data = $band_data->discography[$i];
|
||||
|
||||
// 'a' or 't' for albums and individual tracks respectively
|
||||
$tralbum_type = substr($album_basic_data->item_type, 0, 1);
|
||||
|
||||
$query_data = array(
|
||||
'band_id' => $band_id,
|
||||
'tralbum_type' => $tralbum_type,
|
||||
'tralbum_id' => $album_basic_data->item_id
|
||||
);
|
||||
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
|
||||
}
|
||||
break;
|
||||
case 'By album':
|
||||
$regex = '/album=(\d+)/';
|
||||
if(preg_match($regex, $html, $matches) == false)
|
||||
returnServerError('Unable to find album ID on: ' . $this->getURI());
|
||||
$album_id = $matches[1];
|
||||
|
||||
$query_data = array(
|
||||
'band_id' => $band_id,
|
||||
'tralbum_type' => 'a',
|
||||
'tralbum_id' => $album_id
|
||||
);
|
||||
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
foreach ($tralbums as $tralbum_data) {
|
||||
if ($tralbum_data->type === 'a' && $this->getInput('type') === 'tracks') {
|
||||
foreach ($tralbum_data->tracks as $track) {
|
||||
$query_data = array(
|
||||
'band_id' => $band_id,
|
||||
'tralbum_type' => 't',
|
||||
'tralbum_id' => $track->track_id
|
||||
);
|
||||
$track_data = $this->apiGet('mobile/22/tralbum_details', $query_data);
|
||||
|
||||
$this->items[] = $this->buildTralbumItem($track_data);
|
||||
}
|
||||
} else {
|
||||
$this->items[] = $this->buildTralbumItem($tralbum_data);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function buildTralbumItem($tralbum_data){
|
||||
$band_data = $tralbum_data->band;
|
||||
|
||||
// Format title like: ARTIST - ALBUM/TRACK (OPTIONAL RELEASER)
|
||||
// Format artist/author like: ARTIST (OPTIONAL RELEASER)
|
||||
//
|
||||
// If the album/track is released under a label/a band other than the artist
|
||||
// themselves, append that releaser name to the title and artist/author.
|
||||
//
|
||||
// This sadly doesn't always work right for individual tracks as the artist
|
||||
// of the track is always set to the releaser.
|
||||
$artist = $tralbum_data->tralbum_artist;
|
||||
$full_title = $artist . ' - ' . $tralbum_data->title;
|
||||
$full_artist = $artist;
|
||||
if (isset($tralbum_data->label)) {
|
||||
$full_title .= ' (' . $tralbum_data->label . ')';
|
||||
$full_artist .= ' (' . $tralbum_data->label . ')';
|
||||
} elseif ($band_data->name !== $artist) {
|
||||
$full_title .= ' (' . $band_data->name . ')';
|
||||
$full_artist .= ' (' . $band_data->name . ')';
|
||||
}
|
||||
|
||||
$small_img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_300PX);
|
||||
$img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_700PX);
|
||||
|
||||
$item = array(
|
||||
'uri' => $tralbum_data->bandcamp_url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title,
|
||||
'enclosures' => array($img),
|
||||
'timestamp' => $tralbum_data->release_date
|
||||
);
|
||||
|
||||
$item['categories'] = array();
|
||||
foreach ($tralbum_data->tags as $tag) {
|
||||
$item['categories'][] = $tag->norm_name;
|
||||
}
|
||||
|
||||
// Give articles a unique UID depending on its track list
|
||||
// Releases should then show up as new articles when tracks are added
|
||||
if ($this->getInput('type') === 'changes') {
|
||||
$item['uid'] = "bandcamp/$band_data->band_id/$tralbum_data->id/";
|
||||
foreach ($tralbum_data->tracks as $track) {
|
||||
$item['uid'] .= $track->track_id;
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title<br/>";
|
||||
if ($tralbum_data->type === 'a') {
|
||||
$item['content'] .= '<ol>';
|
||||
foreach ($tralbum_data->tracks as $track) {
|
||||
$item['content'] .= "<li>$track->title</li>";
|
||||
}
|
||||
$item['content'] .= '</ol>';
|
||||
}
|
||||
if (!empty($tralbum_data->about)) {
|
||||
$item['content'] .= '<p>'
|
||||
. nl2br($tralbum_data->about)
|
||||
. '</p>';
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function buildRequestJson(){
|
||||
$requestJson = array(
|
||||
'tag' => $this->getInput('tag'),
|
||||
'page' => 1,
|
||||
'sort' => 'date'
|
||||
);
|
||||
return json_encode($requestJson);
|
||||
}
|
||||
|
||||
private function getImageUrl($id, $size){
|
||||
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
|
||||
}
|
||||
|
||||
private function apiGet($endpoint, $query_data) {
|
||||
$url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data);
|
||||
$data = json_decode(getContents($url));
|
||||
return $data;
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return self::URI . 'tag/' . urlencode($this->getInput('tag')) . '?sort_field=date';
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return self::URI
|
||||
. 'tag/'
|
||||
. urlencode($this->getInput('tag'))
|
||||
. '?sort_field=date';
|
||||
}
|
||||
break;
|
||||
case 'By label':
|
||||
if(!is_null($this->getInput('label'))) {
|
||||
return 'https://'
|
||||
. $this->getInput('label')
|
||||
. '.bandcamp.com/music';
|
||||
}
|
||||
break;
|
||||
case 'By band':
|
||||
if(!is_null($this->getInput('band'))) {
|
||||
return 'https://'
|
||||
. $this->getInput('band')
|
||||
. '.bandcamp.com/music';
|
||||
}
|
||||
break;
|
||||
case 'By album':
|
||||
if(!is_null($this->getInput('band')) && !is_null($this->getInput('album'))) {
|
||||
return 'https://'
|
||||
. $this->getInput('band')
|
||||
. '.bandcamp.com/album/'
|
||||
. $this->getInput('album');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return $this->getInput('tag') . ' - Bandcamp Tag';
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
return $this->getInput('tag') . ' - Bandcamp Tag';
|
||||
}
|
||||
break;
|
||||
case 'By band':
|
||||
if(isset($this->feedName)) {
|
||||
return $this->feedName . ' - Bandcamp Band';
|
||||
} elseif(!is_null($this->getInput('band'))) {
|
||||
return $this->getInput('band') . ' - Bandcamp Band';
|
||||
}
|
||||
break;
|
||||
case 'By label':
|
||||
if(isset($this->feedName)) {
|
||||
return $this->feedName . ' - Bandcamp Label';
|
||||
} elseif(!is_null($this->getInput('label'))) {
|
||||
return $this->getInput('label') . ' - Bandcamp Label';
|
||||
}
|
||||
break;
|
||||
case 'By album':
|
||||
if(isset($this->feedName)) {
|
||||
return $this->feedName . ' - Bandcamp Album';
|
||||
} elseif(!is_null($this->getInput('album'))) {
|
||||
return $this->getInput('album') . ' - Bandcamp Album';
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// By tag
|
||||
$regex = '/^(https?:\/\/)?bandcamp\.com\/tag\/([^\/.&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['tag'] = urldecode($matches[2]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By band
|
||||
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['band'] = urldecode($matches[2]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
// By album
|
||||
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['band'] = urldecode($matches[2]);
|
||||
$params['album'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@@ -3,19 +3,12 @@ class BastaBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'qwertygc';
|
||||
const NAME = 'Bastamag Bridge';
|
||||
const URI = 'http://www.bastamag.net/';
|
||||
const URI = 'https://www.bastamag.net/';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
public function collectData(){
|
||||
// Replaces all relative image URLs by absolute URLs.
|
||||
// Relative URLs always start with 'local/'!
|
||||
function replaceImageUrl($content){
|
||||
return preg_replace('/src=["\']{1}([^"\']+)/ims', 'src=\'' . self::URI . '$1\'', $content);
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend')
|
||||
or returnServerError('Could not request Bastamag.');
|
||||
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend');
|
||||
|
||||
$limit = 0;
|
||||
|
||||
@@ -25,7 +18,11 @@ class BastaBridge extends BridgeAbstract {
|
||||
$item['title'] = $element->find('title', 0)->innertext;
|
||||
$item['uri'] = $element->find('guid', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
|
||||
$item['content'] = replaceImageUrl(getSimpleHTMLDOM($item['uri'])->find('div.texte', 0)->innertext);
|
||||
|
||||
$html = getSimpleHTMLDOM($item['uri']);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$item['content'] = $html->find('div.texte', 0)->innertext;
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
|
97
bridges/BinanceBridge.php
Normal file
97
bridges/BinanceBridge.php
Normal file
@@ -0,0 +1,97 @@
|
||||
<?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());
|
||||
|
||||
$appData = $html->find('script[id="__APP_DATA"]');
|
||||
$appDataJson = json_decode($appData[0]->innertext);
|
||||
|
||||
foreach($appDataJson->pageData->redux->blogList->blogList as $element) {
|
||||
|
||||
$date = $element->postTime;
|
||||
$abstract = $element->brief;
|
||||
$uri = self::URI . '/' . $element->lang . '/blog/' . $element->idStr;
|
||||
$title = $element->title;
|
||||
$content = $element->content;
|
||||
|
||||
$item = array();
|
||||
$item['title'] = $title;
|
||||
$item['uri'] = $uri;
|
||||
$item['timestamp'] = substr($date, 0, -3);
|
||||
$item['author'] = 'Binance';
|
||||
$item['content'] = $content;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected function collectAnnouncementData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
@@ -1,31 +1,45 @@
|
||||
<?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) {
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach($html->find('div.blague') as $element) {
|
||||
|
||||
$item = array();
|
||||
$temp = $element->find('a');
|
||||
|
||||
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;
|
||||
}
|
||||
$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;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
29
bridges/BleepingComputerBridge.php
Normal file
29
bridges/BleepingComputerBridge.php
Normal file
@@ -0,0 +1,29 @@
|
||||
<?php
|
||||
class BleepingComputerBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'csisoap';
|
||||
const NAME = 'Bleeping Computer';
|
||||
const URI = 'https://www.bleepingcomputer.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$article_html = getSimpleHTMLDOMCached($item['uri']);
|
||||
if(!$article_html) {
|
||||
$item['content'] .= '<p><em>Could not request ' . $this->getName() . ': ' . $item['uri'] . '</em></p>';
|
||||
return $item;
|
||||
}
|
||||
|
||||
$article_content = $article_html->find('div.articleBody', 0)->innertext;
|
||||
$article_content = stripRecursiveHTMLSection($article_content, 'div', '<div class="cz-related-article-wrapp');
|
||||
$item['content'] = trim($article_content);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$feed = static::URI . 'feed/';
|
||||
$this->collectExpandableDatas($feed);
|
||||
}
|
||||
}
|
60
bridges/BlizzardNewsBridge.php
Normal file
60
bridges/BlizzardNewsBridge.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class BlizzardNewsBridge extends XPathAbstract {
|
||||
|
||||
const NAME = 'Blizzard News';
|
||||
const URI = 'https://news.blizzard.com';
|
||||
const DESCRIPTION = 'Blizzard (game company) newsfeed';
|
||||
const MAINTAINER = 'Niehztog';
|
||||
const PARAMETERS = array(
|
||||
'' => array(
|
||||
'locale' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Deutsch' => 'de-de',
|
||||
'English (EU)' => 'en-gb',
|
||||
'English (US)' => 'en-us',
|
||||
'Español (EU)' => 'es-es',
|
||||
'Español (AL)' => 'es-mx',
|
||||
'Français' => 'fr-fr',
|
||||
'Italiano' => 'it-it',
|
||||
'日本語' => 'ja-jp',
|
||||
'한국어' => 'ko-kr',
|
||||
'Polski' => 'pl-pl',
|
||||
'Português (AL)' => 'pt-br',
|
||||
'Русский' => 'ru-ru',
|
||||
'ภาษาไทย' => 'th-th',
|
||||
'简体中文' => 'zh-cn',
|
||||
'繁體中文' => 'zh-tw'
|
||||
),
|
||||
'defaultValue' => 'en-us',
|
||||
'title' => 'Select your language'
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const XPATH_EXPRESSION_ITEM = '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article';
|
||||
const XPATH_EXPRESSION_ITEM_TITLE = './/div/div[2]/h2';
|
||||
const XPATH_EXPRESSION_ITEM_CONTENT = './/div[@class="ArticleListItem-description"]/div[@class="h6"]';
|
||||
const XPATH_EXPRESSION_ITEM_URI = './/a[@class="ArticleLink ArticleLink"]/@href';
|
||||
const XPATH_EXPRESSION_ITEM_AUTHOR = '';
|
||||
const XPATH_EXPRESSION_ITEM_TIMESTAMP = './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp';
|
||||
const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/div[@class="ArticleListItem-image"]/@style';
|
||||
const XPATH_EXPRESSION_ITEM_CATEGORIES = './/div[@class="ArticleListItem-label"]';
|
||||
const SETTING_FIX_ENCODING = true;
|
||||
|
||||
/**
|
||||
* Source Web page URL (should provide either HTML or XML content)
|
||||
* @return string
|
||||
*/
|
||||
protected function getSourceUrl(){
|
||||
|
||||
$locale = $this->getInput('locale');
|
||||
if('zh-cn' === $locale) {
|
||||
return 'https://cn.news.blizzard.com';
|
||||
}
|
||||
return 'https://news.blizzard.com/' . $locale;
|
||||
}
|
||||
}
|
@@ -1,65 +0,0 @@
|
||||
<?php
|
||||
class BloombergBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bloomberg';
|
||||
const URI = 'https://www.bloomberg.com/';
|
||||
const DESCRIPTION = 'Trending stories from Bloomberg';
|
||||
const MAINTAINER = 'mdemoss';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Trending Stories' => array(),
|
||||
'From Search' => array(
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'required' => true
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getName()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
case 'Trending Stories':
|
||||
return self::NAME . ' Trending Stories';
|
||||
case 'From Search':
|
||||
if (!is_null($this->getInput('q'))) {
|
||||
return self::NAME . ' Search : ' . $this->getInput('q');
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
switch($this->queriedContext) {
|
||||
case 'Trending Stories': // Get list of top new <article>s from the front page.
|
||||
$html = getSimpleHTMLDOMCached($this->getURI(), 300);
|
||||
$stories = $html->find('ul.top-news-v3__stories article.top-news-v3-story');
|
||||
break;
|
||||
case 'From Search': // Get list of <article> elements from search.
|
||||
$html = getSimpleHTMLDOMCached(
|
||||
$this->getURI() .
|
||||
'search?sort=time:desc&page=1&query=' .
|
||||
urlencode($this->getInput('q')), 300
|
||||
);
|
||||
$stories = $html->find('div.search-result-items article.search-result-story');
|
||||
break;
|
||||
}
|
||||
foreach ($stories as $element) {
|
||||
$item['uri'] = $element->find('h1 a', 0)->href;
|
||||
if (preg_match('#^https://#i', $item['uri']) !== 1) {
|
||||
$item['uri'] = $this->getURI() . $item['uri'];
|
||||
}
|
||||
$articleHtml = getSimpleHTMLDOMCached($item['uri']);
|
||||
if (!$articleHtml) {
|
||||
continue;
|
||||
}
|
||||
$item['title'] = $element->find('h1 a', 0)->plaintext;
|
||||
$item['timestamp'] = strtotime($articleHtml->find('meta[name=iso-8601-publish-date],meta[name=date]', 0)->content);
|
||||
$item['content'] = $articleHtml->find('meta[name=description]', 0)->content;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
157
bridges/BrutBridge.php
Normal file
157
bridges/BrutBridge.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
class BrutBridge extends BridgeAbstract {
|
||||
const NAME = 'Brut Bridge';
|
||||
const URI = 'https://www.brut.media';
|
||||
const DESCRIPTION = 'Returns 5 newest videos by category and edition';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'News' => 'news',
|
||||
'International' => 'international',
|
||||
'Economy' => 'economy',
|
||||
'Science and Technology' => 'science-and-technology',
|
||||
'Entertainment' => 'entertainment',
|
||||
'Sports' => 'sport',
|
||||
'Nature' => 'nature',
|
||||
'Health' => 'health',
|
||||
),
|
||||
'defaultValue' => 'news',
|
||||
),
|
||||
'edition' => array(
|
||||
'name' => ' Edition',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'United States' => 'us',
|
||||
'United Kingdom' => 'uk',
|
||||
'France' => 'fr',
|
||||
'Spain' => 'es',
|
||||
'India' => 'in',
|
||||
'Mexico' => 'mx',
|
||||
),
|
||||
'defaultValue' => 'us',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 1800; // 30 mins
|
||||
|
||||
private $videoId = '';
|
||||
private $videoType = '';
|
||||
private $videoImage = '';
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$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);
|
||||
|
||||
$this->videoImage = $videoPageHtml->find('meta[name="twitter:image"]', 0)->content;
|
||||
|
||||
$this->processTwitterImage();
|
||||
|
||||
$description = $videoPageHtml->find('div.description', 0);
|
||||
|
||||
$item['uri'] = $videoPath;
|
||||
$item['title'] = $description->find('h1', 0)->plaintext;
|
||||
|
||||
if ($description->find('div.date', 0)->children(0)) {
|
||||
$description->find('div.date', 0)->children(0)->outertext = '';
|
||||
}
|
||||
|
||||
$item['content'] = $this->processContent(
|
||||
$description
|
||||
);
|
||||
|
||||
$item['timestamp'] = $this->processDate($description);
|
||||
$item['enclosures'][] = $this->videoImage;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
|
||||
return self::URI . '/' . $this->getInput('edition') . '/' . $this->getInput('category');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
|
||||
$parameters = $this->getParameters();
|
||||
|
||||
$editionValues = array_flip($parameters[0]['edition']['values']);
|
||||
$categoryValues = array_flip($parameters[0]['category']['values']);
|
||||
|
||||
return $categoryValues[$this->getInput('category')] . ' - ' .
|
||||
$editionValues[$this->getInput('edition')] . ' - Brut.';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function processDate($description) {
|
||||
|
||||
if ($this->getInput('edition') === 'uk') {
|
||||
$date = DateTime::createFromFormat('d/m/Y H:i', $description->find('div.date', 0)->innertext);
|
||||
return strtotime($date->format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
return strtotime($description->find('div.date', 0)->innertext);
|
||||
}
|
||||
|
||||
private function processContent($description) {
|
||||
|
||||
$content = '<video controls poster="' . $this->videoImage . '" preload="none">
|
||||
<source src="https://content.brut.media/video/' . $this->videoId . '-' . $this->videoType . '-web.mp4"
|
||||
type="video/mp4">
|
||||
</video>';
|
||||
$content .= '<p>' . $description->find('h2.mb-1', 0)->innertext . '</p>';
|
||||
|
||||
if ($description->find('div.text.pb-3', 0)->children(1)->class != 'date') {
|
||||
$content .= '<p>' . $description->find('div.text.pb-3', 0)->children(1)->innertext . '</p>';
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function processTwitterImage() {
|
||||
/**
|
||||
* Extract video ID + type from twitter image
|
||||
*
|
||||
* Example (wrapped):
|
||||
* https://img.brut.media/thumbnail/
|
||||
* the-life-of-rita-moreno-2cce75b5-d448-44d2-a97c-ca50d6470dd4-square.jpg
|
||||
* ?ts=1559337892
|
||||
*/
|
||||
$fpath = parse_url($this->videoImage, PHP_URL_PATH);
|
||||
$fname = basename($fpath);
|
||||
$fname = substr($fname, 0, strrpos($fname, '.'));
|
||||
$parts = explode('-', $fname);
|
||||
|
||||
if (end($parts) === 'auto') {
|
||||
$key = array_search('auto', $parts);
|
||||
unset($parts[$key]);
|
||||
}
|
||||
|
||||
$this->videoId = implode('-', array_splice($parts, -6, 5));
|
||||
$this->videoType = end($parts);
|
||||
}
|
||||
}
|
218
bridges/BukowskisBridge.php
Executable file
218
bridges/BukowskisBridge.php
Executable file
@@ -0,0 +1,218 @@
|
||||
<?php
|
||||
|
||||
class BukowskisBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Bukowskis';
|
||||
const URI = 'https://www.bukowskis.com';
|
||||
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
|
||||
const MAINTAINER = 'Qluxzz';
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All categories' => '',
|
||||
'Art' => array(
|
||||
'All' => 'art',
|
||||
'Classic Art' => 'art.classic-art',
|
||||
'Classic Finnish Art' => 'art.classic-finnish-art',
|
||||
'Classic Swedish Art' => 'art.classic-swedish-art',
|
||||
'Contemporary' => 'art.contemporary',
|
||||
'Modern Finnish Art' => 'art.modern-finnish-art',
|
||||
'Modern International Art' => 'art.modern-international-art',
|
||||
'Modern Swedish Art' => 'art.modern-swedish-art',
|
||||
'Old Masters' => 'art.old-masters',
|
||||
'Other' => 'art.other',
|
||||
'Photographs' => 'art.photographs',
|
||||
'Prints' => 'art.prints',
|
||||
'Sculpture' => 'art.sculpture',
|
||||
'Swedish Old Masters' => 'art.swedish-old-masters',
|
||||
),
|
||||
'Asian Ceramics & Works of Art' => array(
|
||||
'All' => 'asian-ceramics-works-of-art',
|
||||
'Other' => 'asian-ceramics-works-of-art.other',
|
||||
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
|
||||
),
|
||||
'Books & Manuscripts' => array(
|
||||
'All' => 'books-manuscripts',
|
||||
'Books' => 'books-manuscripts.books',
|
||||
),
|
||||
'Carpets, rugs & textiles' => array(
|
||||
'All' => 'carpets-rugs-textiles',
|
||||
'European' => 'carpets-rugs-textiles.european',
|
||||
'Oriental' => 'carpets-rugs-textiles.oriental',
|
||||
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
|
||||
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
|
||||
),
|
||||
'Ceramics & porcelain' => array(
|
||||
'All' => 'ceramics-porcelain',
|
||||
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
|
||||
'European' => 'ceramics-porcelain.european',
|
||||
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
|
||||
'Scandinavian' => 'ceramics-porcelain.scandinavian',
|
||||
),
|
||||
'Collectibles' => array(
|
||||
'All' => 'collectibles',
|
||||
'Advertising & Retail' => 'collectibles.advertising-retail',
|
||||
'Memorabilia' => 'collectibles.memorabilia',
|
||||
'Movies & music' => 'collectibles.movies-music',
|
||||
'Other' => 'collectibles.other',
|
||||
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
|
||||
'Technica & Nautica' => 'collectibles.technica-nautica',
|
||||
'Toys' => 'collectibles.toys',
|
||||
),
|
||||
'Design' => array(
|
||||
'All' => 'design',
|
||||
'Art glass' => 'design.art-glass',
|
||||
'Furniture' => 'design.furniture',
|
||||
'Other' => 'design.other',
|
||||
),
|
||||
'Folk art' => array(
|
||||
'All' => 'folk-art',
|
||||
'All categories' => 'lots',
|
||||
),
|
||||
'Furniture' => array(
|
||||
'All' => 'furniture',
|
||||
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
|
||||
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
|
||||
'Chairs' => 'furniture.chairs',
|
||||
'Garden furniture' => 'furniture.garden-furniture',
|
||||
'Mirrors' => 'furniture.mirrors',
|
||||
'Other' => 'furniture.other',
|
||||
'Shelves & Book cases' => 'furniture.shelves-book-cases',
|
||||
'Tables' => 'furniture.tables',
|
||||
),
|
||||
'Glassware' => array(
|
||||
'All' => 'glassware',
|
||||
'Glassware' => 'glassware.glassware',
|
||||
'Other' => 'glassware.other',
|
||||
),
|
||||
'Jewellery' => array(
|
||||
'All' => 'jewellery',
|
||||
'Bracelets' => 'jewellery.bracelets',
|
||||
'Brooches' => 'jewellery.brooches',
|
||||
'Earrings' => 'jewellery.earrings',
|
||||
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
|
||||
'Other' => 'jewellery.other',
|
||||
'Rings' => 'jewellery.rings',
|
||||
),
|
||||
'Lighting' => array(
|
||||
'All' => 'lighting',
|
||||
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
|
||||
'Ceiling lights' => 'lighting.ceiling-lights',
|
||||
'Chandeliers' => 'lighting.chandeliers',
|
||||
'Floor lights' => 'lighting.floor-lights',
|
||||
'Other' => 'lighting.other',
|
||||
'Table lights' => 'lighting.table-lights',
|
||||
'Wall lights' => 'lighting.wall-lights',
|
||||
),
|
||||
'Militaria' => array(
|
||||
'All' => 'militaria',
|
||||
'Honors & Medals' => 'militaria.honors-medals',
|
||||
'Other militaria' => 'militaria.other-militaria',
|
||||
'Weaponry' => 'militaria.weaponry',
|
||||
),
|
||||
'Miscellaneous' => array(
|
||||
'All' => 'miscellaneous',
|
||||
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
|
||||
'Nickel silver' => 'miscellaneous.nickel-silver',
|
||||
'Oriental' => 'miscellaneous.oriental',
|
||||
'Other' => 'miscellaneous.other',
|
||||
),
|
||||
'Silver' => array(
|
||||
'All' => 'silver',
|
||||
'Candle sticks' => 'silver.candle-sticks',
|
||||
'Cups & Bowls' => 'silver.cups-bowls',
|
||||
'Cutlery' => 'silver.cutlery',
|
||||
'Other' => 'silver.other',
|
||||
),
|
||||
'Timepieces' => array(
|
||||
'All' => 'timepieces',
|
||||
'Other' => 'timepieces.other',
|
||||
'Pocket watches' => 'timepieces.pocket-watches',
|
||||
'Table clocks' => 'timepieces.table-clocks',
|
||||
'Wrist watches' => 'timepieces.wrist-watches',
|
||||
),
|
||||
'Vintage & Fashion' => array(
|
||||
'All' => 'vintage-fashion',
|
||||
'Accessories' => 'vintage-fashion.accessories',
|
||||
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
|
||||
'Clothes' => 'vintage-fashion.clothes',
|
||||
),
|
||||
)
|
||||
),
|
||||
'sort_order' => array(
|
||||
'name' => 'Sort order',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Ending soon' => 'ending',
|
||||
'Most recent' => 'recent',
|
||||
'Most bids' => 'most',
|
||||
'Fewest bids' => 'fewest',
|
||||
'Lowest price' => 'lowest',
|
||||
'Highest price' => 'highest',
|
||||
'Lowest estimate' => 'low',
|
||||
'Highest estimate' => 'high',
|
||||
'Alphabetical' => 'alphabetical',
|
||||
),
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Swedish' => 'sv',
|
||||
'Finnish' => 'fi'
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
private $title;
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$baseUrl = 'https://www.bukowskis.com';
|
||||
$category = $this->getInput('category');
|
||||
$language = $this->getInput('language');
|
||||
$sort_order = $this->getInput('sort_order');
|
||||
|
||||
$url = $baseUrl . '/' . $language . '/lots';
|
||||
|
||||
if ($category)
|
||||
$url = $url . '/category/' . $category;
|
||||
|
||||
if ($sort_order)
|
||||
$url = $url . '/sort/' . $sort_order;
|
||||
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
|
||||
|
||||
foreach ($html->find('div.c-lot-index-lot') as $lot) {
|
||||
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
|
||||
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
|
||||
$images = json_decode(
|
||||
htmlspecialchars_decode(
|
||||
$lot
|
||||
->find('img.o-aspect-ratio__image', 0)
|
||||
->getAttribute('data-thumbnails')
|
||||
)
|
||||
);
|
||||
|
||||
$this->items[] = array(
|
||||
'title' => $title,
|
||||
'uri' => $baseUrl . $relative_url,
|
||||
'uid' => $lot->getAttribute('data-lot-id'),
|
||||
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
|
||||
'enclosures' => array_slice($images, 1),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
}
|
84
bridges/BundesbankBridge.php
Normal file
84
bridges/BundesbankBridge.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?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());
|
||||
|
||||
$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,106 @@ 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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
}
|
||||
|
63
bridges/CNETFranceBridge.php
Normal file
63
bridges/CNETFranceBridge.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
class CNETFranceBridge extends FeedExpander
|
||||
{
|
||||
const MAINTAINER = 'leomaradan';
|
||||
const NAME = 'CNET France';
|
||||
const URI = 'https://www.cnetfrance.fr/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'CNET France RSS with filters';
|
||||
const PARAMETERS = array(
|
||||
'filters' => array(
|
||||
'title' => array(
|
||||
'name' => 'Exclude by title',
|
||||
'required' => false,
|
||||
'title' => 'Title term, separated by semicolon (;)',
|
||||
'defaultValue' => 'bon plan;bons plans;au meilleur prix;des meilleures offres;Amazon Prime Day;RED by SFR ou B&You'
|
||||
),
|
||||
'url' => array(
|
||||
'name' => 'Exclude by url',
|
||||
'required' => false,
|
||||
'title' => 'URL term, separated by semicolon (;)',
|
||||
'defaultValue' => 'bon-plan;bons-plans'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $bannedTitle = array();
|
||||
private $bannedURL = array();
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$title = $this->getInput('title');
|
||||
$url = $this->getInput('url');
|
||||
|
||||
if ($title !== null) {
|
||||
$this->bannedTitle = explode(';', $title);
|
||||
}
|
||||
|
||||
if ($url !== null) {
|
||||
$this->bannedURL = explode(';', $url);
|
||||
}
|
||||
|
||||
$this->collectExpandableDatas('https://www.cnetfrance.fr/feeds/rss/news/');
|
||||
}
|
||||
|
||||
protected function parseItem($feedItem)
|
||||
{
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
foreach ($this->bannedTitle as $term) {
|
||||
if (preg_match('/' . $term . '/mi', $item['title']) === 1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->bannedURL as $term) {
|
||||
if (preg_match('/' . $term . '/mi', $item['uri']) === 1) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
140
bridges/CVEDetailsBridge.php
Normal file
140
bridges/CVEDetailsBridge.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
// CVE Details is a collection of CVEs, taken from the National Vulnerability
|
||||
// Database (NVD) and other sources like the Exploit DB and Metasploit. The
|
||||
// website categorizes it by vendor and product and attach the CWE category.
|
||||
// There is a Atom feed available, but only logged in users can use it,
|
||||
// it is not reliable and contain no useful information. This bridge create a
|
||||
// sane feed with additional information like tags and a link to the CWE
|
||||
// a description of the vulnerability.
|
||||
class CVEDetailsBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'Aaron Fischer';
|
||||
const NAME = 'CVE Details';
|
||||
const CACHE_TIMEOUT = 60 * 60 * 6; // 6 hours
|
||||
const DESCRIPTION = 'Report new CVE vulnerabilities for a given vendor (and product)';
|
||||
const URI = 'https://www.cvedetails.com';
|
||||
|
||||
const PARAMETERS = array(array(
|
||||
// The Vendor ID can be taken from the URL
|
||||
'vendor_id' => array(
|
||||
'name' => 'Vendor ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => 74, // PHP
|
||||
),
|
||||
// The optional Product ID can be taken from the URL as well
|
||||
'product_id' => array(
|
||||
'name' => 'Product ID',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'exampleValue' => 128, // PHP
|
||||
),
|
||||
));
|
||||
|
||||
private $html = null;
|
||||
private $vendor = '';
|
||||
private $product = '';
|
||||
|
||||
// Return the URL to query.
|
||||
// Because of the optional product ID, we need to attach it if it is
|
||||
// set. The search result page has the exact same structure (with and
|
||||
// without the product ID).
|
||||
private function _buildURL() {
|
||||
$url = self::URI . '/vulnerability-list/vendor_id-' . $this->getInput('vendor_id');
|
||||
if ($this->getInput('product_id') !== '') {
|
||||
$url .= '/product_id-' . $this->getInput('product_id');
|
||||
}
|
||||
// Sadly, there is no way (prove me wrong please) to sort the search
|
||||
// result by publish date. So the nearest alternative is the CVE
|
||||
// number, which should be mostly accurate.
|
||||
$url .= '?order=1'; // Order by CVE number DESC
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Make the actual request to cvedetails.com and stores the response
|
||||
// (HTML) for later use and extract vendor and product from it.
|
||||
private function _fetchContent() {
|
||||
$html = getSimpleHTMLDOM($this->_buildURL());
|
||||
$this->html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$vendor = $html->find('#contentdiv > h1 > a', 0);
|
||||
if ($vendor == null) {
|
||||
returnServerError('Invalid Vendor ID ' .
|
||||
$this->getInput('vendor_id') .
|
||||
' or Product ID ' .
|
||||
$this->getInput('product_id'));
|
||||
}
|
||||
$this->vendor = $vendor->innertext;
|
||||
|
||||
$product = $html->find('#contentdiv > h1 > a', 1);
|
||||
if ($product != null) {
|
||||
$this->product = $product->innertext;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the name of the feed.
|
||||
public function getName() {
|
||||
if ($this->getInput('vendor_id') == '') {
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
if ($this->html == null) {
|
||||
$this->_fetchContent();
|
||||
}
|
||||
|
||||
$name = 'CVE Vulnerabilities for ' . $this->vendor;
|
||||
if ($this->product != '') {
|
||||
$name .= '/' . $this->product;
|
||||
}
|
||||
|
||||
return $name;
|
||||
}
|
||||
|
||||
// Pull the data from the HTML response and fill the items..
|
||||
public function collectData() {
|
||||
if ($this->html == null) {
|
||||
$this->_fetchContent();
|
||||
}
|
||||
|
||||
foreach ($this->html->find('#vulnslisttable .srrowns') as $i => $tr) {
|
||||
// There are some optional vulnerability types, which will be
|
||||
// added to the categories as well as the CWE number -- which is
|
||||
// always given.
|
||||
$categories = array($this->vendor);
|
||||
$enclosures = array();
|
||||
|
||||
$cwe = $tr->find('td', 2)->find('a', 0);
|
||||
if ($cwe != null) {
|
||||
$cwe = $cwe->innertext;
|
||||
$categories[] = 'CWE-' . $cwe;
|
||||
$enclosures[] = 'https://cwe.mitre.org/data/definitions/' . $cwe . '.html';
|
||||
}
|
||||
$c = $tr->find('td', 4)->innertext;
|
||||
if (trim($c) != '') {
|
||||
$categories[] = $c;
|
||||
}
|
||||
if ($this->product != '') {
|
||||
$categories[] = $this->product;
|
||||
}
|
||||
|
||||
// The CVE number itself
|
||||
$title = $tr->find('td', 1)->find('a', 0)->innertext;
|
||||
|
||||
$this->items[] = array(
|
||||
'uri' => $tr->find('td', 1)->find('a', 0)->href,
|
||||
'title' => $title,
|
||||
'timestamp' => $tr->find('td', 5)->innertext,
|
||||
'content' => $tr->next_sibling()->innertext,
|
||||
'categories' => $categories,
|
||||
'enclosures' => $enclosures,
|
||||
'uid' => $tr->find('td', 1)->find('a', 0)->innertext,
|
||||
);
|
||||
|
||||
// We only want to fetch the latest 10 CVEs
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
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 = array();
|
||||
|
||||
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 = array();
|
||||
$categories[] = $incident->human_status;
|
||||
if ($componentName !== '') {
|
||||
$categories[] = $componentName;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $permalink;
|
||||
$item['title'] = $title;
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['content'] = $content;
|
||||
$item['uid'] = $uid;
|
||||
$item['categories'] = $categories;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -2,7 +2,7 @@
|
||||
class CastorusBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const NAME = 'Castorus Bridge';
|
||||
const URI = 'http://www.castorus.com';
|
||||
const URI = 'https://www.castorus.com';
|
||||
const CACHE_TIMEOUT = 600; // 10min
|
||||
const DESCRIPTION = 'Returns the latest changes';
|
||||
|
||||
@@ -83,7 +83,7 @@ class CastorusBridge extends BridgeAbstract {
|
||||
if(!$html)
|
||||
returnServerError('Could not load data from ' . self::URI . '!');
|
||||
|
||||
$activities = $html->find('div#activite/li');
|
||||
$activities = $html->find('div#activite > li');
|
||||
|
||||
if(!$activities)
|
||||
returnServerError('Failed to find activities!');
|
||||
|
83
bridges/CeskaTelevizeBridge.php
Normal file
83
bridges/CeskaTelevizeBridge.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
|
||||
class CeskaTelevizeBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Česká televize Bridge';
|
||||
const URI = 'https://www.ceskatelevize.cz';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'Return newest videos';
|
||||
const MAINTAINER = 'kolarcz';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'url' => array(
|
||||
'name' => 'url to the show',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function fixChars($text) {
|
||||
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
private function getUploadTimeFromString($string) {
|
||||
if (strpos($string, 'dnes') !== false) {
|
||||
return strtotime('today');
|
||||
} elseif (strpos($string, 'včera') !== false) {
|
||||
return strtotime('yesterday');
|
||||
} elseif (!preg_match('/(\d+).\s(\d+).(\s(\d+))?/', $string, $match)) {
|
||||
returnServerError('Could not get date from Česká televize string');
|
||||
}
|
||||
|
||||
$date = sprintf('%04d-%02d-%02d', isset($match[3]) ? $match[3] : date('Y'), $match[2], $match[1]);
|
||||
return strtotime($date);
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
|
||||
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(bonus\/)?$/';
|
||||
if (!preg_match($validUrl, $url, $match)) {
|
||||
returnServerError('Invalid url');
|
||||
}
|
||||
|
||||
$category = isset($match[4]) ? $match[4] : 'nove';
|
||||
$fixedUrl = "{$match[1]}dily/{$category}/";
|
||||
|
||||
$html = getSimpleHTMLDOM($fixedUrl);
|
||||
|
||||
$this->feedUri = $fixedUrl;
|
||||
$this->feedName = str_replace('Přehled dílů — ', '', $this->fixChars($html->find('title', 0)->plaintext));
|
||||
if ($category !== 'nove') {
|
||||
$this->feedName .= " ({$category})";
|
||||
}
|
||||
|
||||
foreach ($html->find('#episodeListSection a[data-testid=next-link]') as $element) {
|
||||
$itemTitle = $element->find('h3', 0);
|
||||
$itemContent = $element->find('div[class^=content-]', 0);
|
||||
$itemDate = $element->find('div[class^=playTime-] span', 0);
|
||||
$itemThumbnail = $element->find('img', 0);
|
||||
$itemUri = self::URI . $element->getAttribute('href');
|
||||
|
||||
$item = array(
|
||||
'title' => $this->fixChars($itemTitle->plaintext),
|
||||
'uri' => $itemUri,
|
||||
'content' => '<img src="' . $itemThumbnail->getAttribute('src') . '" /><br />'
|
||||
. $this->fixChars($itemContent->plaintext),
|
||||
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext)
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return isset($this->feedName) ? $this->feedName : parent::getName();
|
||||
}
|
||||
}
|
@@ -1,25 +0,0 @@
|
||||
<?php
|
||||
class ChristianDailyReporterBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'rogerdc';
|
||||
const NAME = 'Christian Daily Reporter Unofficial RSS';
|
||||
const URI = 'https://www.christiandailyreporter.com/';
|
||||
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
|
||||
// const CACHE_TIMEOUT = 86400; // 1 day
|
||||
|
||||
|
||||
public function collectData() {
|
||||
$uri = 'https://www.christiandailyreporter.com/';
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Christian Daily Reporter.');
|
||||
foreach($html->find('div.top p a,div.column p a') as $element) {
|
||||
$item = array();
|
||||
// Title
|
||||
$item['title'] = $element->innertext;
|
||||
// URL
|
||||
$item['uri'] = $element->href;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
387
bridges/CodebergBridge.php
Normal file
387
bridges/CodebergBridge.php
Normal file
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
class CodebergBridge extends BridgeAbstract {
|
||||
const NAME = 'Codeberg Bridge';
|
||||
const URI = 'https://codeberg.org/';
|
||||
const DESCRIPTION = 'Returns commits, issues, pull requests or releases for a repository.';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'Commits' => array(
|
||||
'branch' => array(
|
||||
'name' => 'branch',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'main',
|
||||
'required' => false,
|
||||
'title' => 'Optional, main branch is used by default.',
|
||||
),
|
||||
),
|
||||
'Issues' => array(),
|
||||
'Issue Comments' => array(
|
||||
'issueId' => array(
|
||||
'name' => 'Issue ID',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
)
|
||||
),
|
||||
'Pull Requests' => array(),
|
||||
'Releases' => array(),
|
||||
'global' => array(
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'username',
|
||||
'title' => 'Username of account that the repository belongs to.',
|
||||
'required' => true,
|
||||
),
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'repo',
|
||||
'required' => true,
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
|
||||
const TEST_DETECT_PARAMETERS = array(
|
||||
'https://codeberg.org/Codeberg/Community/issues/507' => array(
|
||||
'context' => 'Issue Comments', 'username' => 'Codeberg', 'repo' => 'Community', 'issueId' => '507'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/issues' => array(
|
||||
'context' => 'Issues', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/pulls' => array(
|
||||
'context' => 'Pull Requests', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/releases' => array(
|
||||
'context' => 'Releases', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/commits/branch/master' => array(
|
||||
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community', 'branch' => 'master'
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/commits' => array(
|
||||
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
)
|
||||
);
|
||||
|
||||
private $defaultBranch = 'main';
|
||||
private $issueTitle = '';
|
||||
|
||||
private $urlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)(?:\/commits\/branch\/([\w]+))?/';
|
||||
private $issuesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues/';
|
||||
private $pullsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/pulls/';
|
||||
private $releasesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/releases/';
|
||||
private $issueCommentsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues\/([0-9]+)/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// Issue Comments
|
||||
if(preg_match($this->issueCommentsUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Issue Comments';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
$params['issueId'] = $matches[3];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Issues
|
||||
if(preg_match($this->issuesUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Issues';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Pull Requests
|
||||
if(preg_match($this->pullsUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Pull Requests';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Releases
|
||||
if(preg_match($this->releasesUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Releases';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
// Commits
|
||||
if(preg_match($this->urlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Commits';
|
||||
$params['username'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
|
||||
if (isset($matches[3])) {
|
||||
$params['branch'] = $matches[3];
|
||||
}
|
||||
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
$this->extractCommits($html);
|
||||
break;
|
||||
case 'Issues':
|
||||
$this->extractIssues($html);
|
||||
break;
|
||||
case 'Issue Comments':
|
||||
$this->extractIssueComments($html);
|
||||
break;
|
||||
case 'Pull Requests':
|
||||
$this->extractPulls($html);
|
||||
break;
|
||||
case 'Releases':
|
||||
$this->extractReleases($html);
|
||||
break;
|
||||
default:
|
||||
returnClientError('Invalid context: ' . $this->queriedContext);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
if ($this->getBranch() === $this->defaultBranch) {
|
||||
return $this->getRepo() . ' Commits';
|
||||
}
|
||||
|
||||
return $this->getRepo() . ' Commits (' . $this->getBranch() . ' branch) - ' . self::NAME;
|
||||
case 'Issues':
|
||||
return $this->getRepo() . ' Issues - ' . self::NAME;
|
||||
case 'Issue Comments':
|
||||
return $this->issueTitle . ' - Issue Comments - ' . self::NAME;
|
||||
case 'Pull Requests':
|
||||
return $this->getRepo() . ' Pull Requests - ' . self::NAME;
|
||||
case 'Releases':
|
||||
return $this->getRepo() . ' Releases - ' . self::NAME;
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
return self::URI . $this->getRepo() . '/commits/branch/' . $this->getBranch();
|
||||
case 'Issues':
|
||||
return self::URI . $this->getRepo() . '/issues/';
|
||||
case 'Issue Comments':
|
||||
return self::URI . $this->getRepo() . '/issues/' . $this->getInput('issueId');
|
||||
case 'Pull Requests':
|
||||
return self::URI . $this->getRepo() . '/pulls';
|
||||
case 'Releases':
|
||||
return self::URI . $this->getRepo() . '/releases';
|
||||
default:
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
private function getBranch() {
|
||||
if ($this->getInput('branch')) {
|
||||
return $this->getInput('branch');
|
||||
}
|
||||
|
||||
return $this->defaultBranch;
|
||||
}
|
||||
|
||||
private function getRepo() {
|
||||
return $this->getInput('username') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
private function extractCommits($html) {
|
||||
$table = $html->find('table#commits-table', 0);
|
||||
$tbody = $table->find('tbody.commit-list', 0);
|
||||
|
||||
foreach ($tbody->find('tr') as $tr) {
|
||||
$item = array();
|
||||
|
||||
$message = $tr->find('td.message', 0);
|
||||
|
||||
$item['title'] = $message->find('span.message-wrapper', 0)->plaintext;
|
||||
$item['uri'] = $tr->find('td.sha', 0)->find('a', 0)->href;
|
||||
$item['author'] = $tr->find('td.author', 0)->plaintext;
|
||||
$item['timestamp'] = $tr->find('td', 3)->find('span', 0)->title;
|
||||
|
||||
if ($message->find('pre.commit-body', 0)) {
|
||||
$message->find('pre.commit-body', 0)->style = '';
|
||||
|
||||
$item['content'] = $message->find('pre.commit-body', 0);
|
||||
} else {
|
||||
$item['content'] = '<blockquote>' . $item['title'] . '</blockquote>';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractIssues($html) {
|
||||
$div = $html->find('div.repository', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = array();
|
||||
|
||||
$number = $li->find('div', 0)->plaintext;
|
||||
|
||||
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
|
||||
$item['uri'] = $li->find('a.title', 0)->href;
|
||||
$item['timestamp'] = $li->find('p.desc', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('p.desc', 0)->find('a', 0)->plaintext;
|
||||
|
||||
// Fetch issue page
|
||||
$issuePage = getSimpleHTMLDOMCached($item['uri'], 3600);
|
||||
|
||||
$issuePage = defaultLinkTo($issuePage, self::URI);
|
||||
|
||||
$item['content'] = $issuePage->find('ui.timeline', 0)->find('div.render-content.markdown', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractIssueComments($html) {
|
||||
$this->issueTitle = $html->find('span#issue-title', 0)->plaintext
|
||||
. ' (' . $html->find('span.index', 0)->plaintext . ')';
|
||||
|
||||
foreach ($html->find('ui.timeline > div.timeline-item.comment') as $div) {
|
||||
$item = array();
|
||||
|
||||
if ($div->class === 'timeline-item comment merge box') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = $this->ellipsisTitle($div->find('div.render-content.markdown', 0)->plaintext);
|
||||
$item['uri'] = $div->find('span.text.grey', 0)->find('a', 1)->href;
|
||||
$item['content'] = $div->find('div.render-content.markdown', 0);
|
||||
|
||||
if ($div->find('div.dropzone-attachments', 0)) {
|
||||
$item['content'] .= $div->find('div.dropzone-attachments', 0);
|
||||
}
|
||||
|
||||
$item['author'] = $div->find('a.author', 0)->innertext;
|
||||
$item['timestamp'] = $div->find('span.time-since', 0)->title;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractPulls($html) {
|
||||
$div = $html->find('div.repository', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = array();
|
||||
|
||||
$number = $li->find('div', 0)->plaintext;
|
||||
|
||||
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
|
||||
$item['uri'] = $li->find('a.title', 0)->href;
|
||||
$item['timestamp'] = $li->find('p.desc', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('p.desc', 0)->find('a', 0)->plaintext;
|
||||
|
||||
// Fetch pull request page
|
||||
$pullRequestPage = getSimpleHTMLDOMCached($item['uri'], 3600);
|
||||
|
||||
$pullRequestPage = defaultLinkTo($pullRequestPage, self::URI);
|
||||
|
||||
$item['content'] = $pullRequestPage->find('ui.timeline', 0)->find('div.render-content.markdown', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractReleases($html) {
|
||||
$ul = $html->find('ul#release-list', 0);
|
||||
|
||||
foreach ($ul->find('li.ui.grid') as $li) {
|
||||
$item = array();
|
||||
|
||||
if ($li->find('h3', 0)) { // Release
|
||||
$item['title'] = $li->find('h3', 0)->plaintext;
|
||||
$item['uri'] = $li->find('h3', 0)->find('a', 0)->href;
|
||||
|
||||
$tag = $li->find('span.tag', 0)->find('a', 0);
|
||||
$commit = $li->find('span.commit', 0);
|
||||
$downloads = $this->extractDownloads($li->find('div.download', 0));
|
||||
|
||||
$item['content'] = $li->find('div.markdown', 0);
|
||||
$item['content'] .= <<<HTML
|
||||
<strong>Tag</strong>
|
||||
<p>{$tag}</p>
|
||||
<strong>Commit</strong>
|
||||
<p>{$commit}</p>
|
||||
{$downloads}
|
||||
HTML;
|
||||
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('span.author', 0)->find('a', 0)->plaintext;
|
||||
}
|
||||
|
||||
if ($li->find('h4', 0)) { // Tag
|
||||
$item['title'] = $li->find('h4', 0)->plaintext;
|
||||
$item['uri'] = $li->find('h4', 0)->find('a', 0)->href;
|
||||
|
||||
$item['content'] = <<<HTML
|
||||
<strong>Commit</strong>
|
||||
<p>{$li->find('div.download', 0)->find('a', 0)}</p>
|
||||
HTML;
|
||||
|
||||
$item['content'] .= $this->extractDownloads($li->find('div.download', 0), true);
|
||||
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function extractDownloads($html, $skipFirst = false) {
|
||||
$downloads = '';
|
||||
|
||||
foreach ($html->find('a') as $index => $a) {
|
||||
if ($skipFirst === true && $index === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$downloads .= <<<HTML
|
||||
{$a}<br>
|
||||
HTML;
|
||||
}
|
||||
|
||||
return <<<EOD
|
||||
<strong>Downloads</strong>
|
||||
<p>{$downloads}</p>
|
||||
EOD;
|
||||
}
|
||||
|
||||
private function ellipsisTitle($text) {
|
||||
$length = 100;
|
||||
|
||||
if (strlen($text) > $length) {
|
||||
$text = explode('<br>', wordwrap($text, $length, '<br>'));
|
||||
return $text[0] . '...';
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
}
|
@@ -3,7 +3,7 @@ class CollegeDeFranceBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'pit-fgfjiudghdf';
|
||||
const NAME = 'CollegeDeFrance';
|
||||
const URI = 'http://www.college-de-france.fr/';
|
||||
const URI = 'https://www.college-de-france.fr/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const DESCRIPTION = 'Returns the latest audio and video from CollegeDeFrance';
|
||||
|
||||
@@ -34,8 +34,7 @@ class CollegeDeFranceBridge extends BridgeAbstract {
|
||||
* </li>
|
||||
*/
|
||||
$html = getSimpleHTMLDOM(self::URI
|
||||
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all')
|
||||
or returnServerError('Could not request CollegeDeFrance.');
|
||||
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all');
|
||||
|
||||
foreach($html->find('a[data-target]') as $element) {
|
||||
$item = array();
|
||||
|
25
bridges/ComboiosDePortugalBridge.php
Normal file
25
bridges/ComboiosDePortugalBridge.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?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() {
|
||||
# Do not verify SSL certificate (the server doesn't send the intermediate)
|
||||
# https://github.com/RSS-Bridge/rss-bridge/issues/2397
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos', array(), array(
|
||||
CURLOPT_SSL_VERIFYPEER => 0,
|
||||
));
|
||||
|
||||
foreach($html->find('.warnings-table a') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $element->innertext;
|
||||
$item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href)));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
62
bridges/ComicsKingdomBridge.php
Normal file
62
bridges/ComicsKingdomBridge.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
class ComicsKingdomBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'stjohnjohnson';
|
||||
const NAME = 'Comics Kingdom Unofficial RSS';
|
||||
const URI = 'https://www.comicskingdom.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Comics Kingdom Unofficial RSS';
|
||||
const PARAMETERS = array( array(
|
||||
'comicname' => array(
|
||||
'name' => 'comicname',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI(), array(), array(), true, false);
|
||||
|
||||
// Get author from first page
|
||||
$author = $html->find('div.author p', 0);;
|
||||
|
||||
// Get current date/link
|
||||
$link = $html->find('meta[property=og:url]', 0)->content;
|
||||
for($i = 0; $i < 5; $i++) {
|
||||
$item = array();
|
||||
|
||||
$page = getSimpleHTMLDOM($link);
|
||||
|
||||
$imagelink = $page->find('meta[property=og:image]', 0)->content;
|
||||
$prevSlug = $page->find('slider-arrow[:is-left-arrow=true]', 0);
|
||||
$link = $this->getURI() . '/' . $prevSlug->getAttribute('date-slug');
|
||||
|
||||
$date = explode('/', $link);
|
||||
|
||||
$item['id'] = $imagelink;
|
||||
$item['uri'] = $link;
|
||||
$item['author'] = $author;
|
||||
$item['title'] = 'Comics Kingdom ' . $this->getInput('comicname');
|
||||
$item['timestamp'] = DateTime::createFromFormat('Y-m-d', $date[count($date) - 1])->getTimestamp();
|
||||
$item['content'] = '<img src="' . $imagelink . '" />';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('comicname'))) {
|
||||
return self::URI . urlencode($this->getInput('comicname'));
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('comicname'))) {
|
||||
return $this->getInput('comicname') . ' - Comics Kingdom';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -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(){
|
||||
|
@@ -10,33 +10,35 @@ class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||
const BETA = 'beta';
|
||||
const ALPHA = 'alpha';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'channel' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'channel' => array(
|
||||
'name' => 'Release Channel',
|
||||
'type' => 'list',
|
||||
'required' => true,
|
||||
'defaultValue' => self::STABLE,
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Stable' => self::STABLE,
|
||||
'Beta' => self::BETA,
|
||||
'Alpha' => self::ALPHA,
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getReleaseFeed($jsonUrl) {
|
||||
$json = getContents($jsonUrl)
|
||||
or returnServerError('Could not request Core OS Website.');
|
||||
private function getReleaseFeed($jsonUrl) {
|
||||
$json = getContents($jsonUrl);
|
||||
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 = array();
|
||||
|
||||
$item['uri'] = "https://coreos.com/releases/#$releaseVersion";
|
||||
$item['title'] = $releaseVersion;
|
||||
|
@@ -8,8 +8,7 @@ class CopieDoubleBridge extends BridgeAbstract {
|
||||
const DESCRIPTION = 'CopieDouble';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request CopieDouble.');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$table = $html->find('table table', 2);
|
||||
|
||||
|
@@ -1,55 +1,27 @@
|
||||
<?php
|
||||
class CourrierInternationalBridge extends BridgeAbstract {
|
||||
class CourrierInternationalBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'Courrier International Bridge';
|
||||
const URI = 'http://CourrierInternational.com/';
|
||||
const URI = 'https://www.courrierinternational.com/';
|
||||
const CACHE_TIMEOUT = 300; // 5 min
|
||||
const DESCRIPTION = 'Courrier International bridge';
|
||||
const DESCRIPTION = 'Returns the newest articles';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Error.');
|
||||
$this->collectExpandableDatas(static::URI . 'feed/all/rss.xml', 20);
|
||||
}
|
||||
|
||||
$element = $html->find('article');
|
||||
$article_count = 1;
|
||||
protected function parseItem($feedItem){
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
foreach($element as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->parent->getAttribute('href');
|
||||
|
||||
if(strpos($item['uri'], 'http') === false) {
|
||||
$item['uri'] = self::URI . $item['uri'];
|
||||
}
|
||||
|
||||
$page = getSimpleHTMLDOMCached($item['uri']);
|
||||
|
||||
$content = $page->find('.article-text', 0);
|
||||
|
||||
if(!$content) {
|
||||
$content = $page->find('.depeche-text', 0);
|
||||
}
|
||||
|
||||
$item['content'] = sanitize($content);
|
||||
$item['title'] = strip_tags($article->find('.title', 0));
|
||||
|
||||
$dateTime = date_parse($page->find('time', 0));
|
||||
|
||||
$item['timestamp'] = mktime(
|
||||
$dateTime['hour'],
|
||||
$dateTime['minute'],
|
||||
$dateTime['second'],
|
||||
$dateTime['month'],
|
||||
$dateTime['day'],
|
||||
$dateTime['year']
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
$article_count ++;
|
||||
|
||||
if($article_count > 5)
|
||||
break;
|
||||
$articlePage = getSimpleHTMLDOMCached($feedItem->link);
|
||||
$content = $articlePage->find('.article-text', 0);
|
||||
if(!$content) {
|
||||
$content = $articlePage->find('.depeche-text', 0);
|
||||
}
|
||||
|
||||
$item['content'] = sanitize($content);
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
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;
|
||||
}
|
||||
}
|
@@ -17,8 +17,7 @@ class CryptomeBridge extends BridgeAbstract {
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not request Cryptome.');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$number = $this->getInput('n');
|
||||
|
||||
|
108
bridges/CuriousCatBridge.php
Normal file
108
bridges/CuriousCatBridge.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
class CuriousCatBridge extends BridgeAbstract {
|
||||
const NAME = 'Curious Cat Bridge';
|
||||
const URI = 'https://curiouscat.me';
|
||||
const DESCRIPTION = 'Returns list of newest questions and answers for a user profile';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'koethekoethe',
|
||||
)
|
||||
));
|
||||
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
|
||||
|
||||
$apiJson = getContents($url);
|
||||
|
||||
$apiData = json_decode($apiJson, true);
|
||||
|
||||
foreach($apiData['posts'] as $post) {
|
||||
$item = array();
|
||||
|
||||
$item['author'] = 'Anonymous';
|
||||
|
||||
if ($post['senderData']['id'] !== false) {
|
||||
$item['author'] = $post['senderData']['username'];
|
||||
}
|
||||
|
||||
$item['uri'] = $this->getURI() . '/post/' . $post['id'];
|
||||
$item['title'] = $this->ellipsisTitle($post['comment']);
|
||||
|
||||
$item['content'] = $this->processContent($post);
|
||||
$item['timestamp'] = $post['timestamp'];
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('username'))) {
|
||||
return self::URI . '/' . $this->getInput('username');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('username'))) {
|
||||
return $this->getInput('username') . ' - Curious Cat';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function processContent($post) {
|
||||
|
||||
$author = 'Anonymous';
|
||||
|
||||
if ($post['senderData']['id'] !== false) {
|
||||
$authorUrl = self::URI . '/' . $post['senderData']['username'];
|
||||
|
||||
$author = <<<EOD
|
||||
<a href="{$authorUrl}">{$post['senderData']['username']}</a>
|
||||
EOD;
|
||||
}
|
||||
|
||||
$question = $this->formatUrls($post['comment']);
|
||||
$answer = $this->formatUrls($post['reply']);
|
||||
|
||||
$content = <<<EOD
|
||||
<p>{$author} asked:</p>
|
||||
<blockquote>{$question}</blockquote><br/>
|
||||
<p>{$post['addresseeData']['username']} answered:</p>
|
||||
<blockquote>{$answer}</blockquote>
|
||||
EOD;
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function ellipsisTitle($text) {
|
||||
$length = 150;
|
||||
|
||||
if (strlen($text) > $length) {
|
||||
$text = explode('<br>', wordwrap($text, $length, '<br>'));
|
||||
return $text[0] . '...';
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private function formatUrls($content) {
|
||||
|
||||
return preg_replace(
|
||||
'/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims',
|
||||
'<a target="_blank" href="$1" target="_blank">$1</a> ',
|
||||
$content
|
||||
);
|
||||
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Dailymotion Bridge';
|
||||
const URI = 'https://www.dailymotion.com/';
|
||||
const CACHE_TIMEOUT = 10800; // 3h
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns the 5 newest videos by username/playlist or search';
|
||||
|
||||
const PARAMETERS = array (
|
||||
@@ -27,70 +27,97 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
),
|
||||
'pa' => array(
|
||||
'name' => 'Page',
|
||||
'type' => 'number'
|
||||
'type' => 'number',
|
||||
'defaultValue' => 1,
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
protected function getMetadata($id){
|
||||
$metadata = array();
|
||||
$html2 = getSimpleHTMLDOM(self::URI . 'video/' . $id);
|
||||
if(!$html2) {
|
||||
return $metadata;
|
||||
}
|
||||
private $feedName = '';
|
||||
|
||||
$metadata['title'] = $html2->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
$metadata['timestamp'] = strtotime(
|
||||
$html2->find('meta[property=video:release_date]', 0)->getAttribute('content')
|
||||
);
|
||||
$metadata['thumbnailUri'] = $html2->find('meta[property=og:image]', 0)->getAttribute('content');
|
||||
$metadata['uri'] = $html2->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||
return $metadata;
|
||||
private $apiUrl = 'https://api.dailymotion.com';
|
||||
private $apiFields = 'created_time,description,id,owner.screenname,tags,thumbnail_url,title,url';
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$limit = 5;
|
||||
$count = 0;
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request Dailymotion.');
|
||||
if ($this->queriedContext === 'By username' || $this->queriedContext === 'By playlist id') {
|
||||
|
||||
foreach($html->find('div.media a.preview_link') as $element) {
|
||||
if($count < $limit) {
|
||||
$apiJson = getContents($this->getApiUrl());
|
||||
|
||||
$apiData = json_decode($apiJson, true);
|
||||
|
||||
$this->feedName = $this->getPlaylistTitle($this->getInput('p'));
|
||||
|
||||
foreach ($apiData['list'] as $apiItem) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $apiItem['url'];
|
||||
$item['uid'] = $apiItem['id'];
|
||||
$item['title'] = $apiItem['title'];
|
||||
$item['timestamp'] = $apiItem['created_time'];
|
||||
$item['author'] = $apiItem['owner.screenname'];
|
||||
$item['content'] = '<p><a href="' . $apiItem['url'] . '">
|
||||
<img src="' . $apiItem['thumbnail_url'] . '"></a></p><p>' . $apiItem['description'] . '</p>';
|
||||
$item['categories'] = $apiItem['tags'];
|
||||
$item['enclosures'][] = $apiItem['thumbnail_url'];
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->queriedContext === 'From search results') {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
foreach($html->find('div.media a.preview_link') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item['id'] = str_replace('/video/', '', strtok($element->href, '_'));
|
||||
$metadata = $this->getMetadata($item['id']);
|
||||
|
||||
if(empty($metadata)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['uri'] = $metadata['uri'];
|
||||
$item['title'] = $metadata['title'];
|
||||
$item['timestamp'] = $metadata['timestamp'];
|
||||
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $metadata['thumbnailUri']
|
||||
. '" /></a><br><a href="'
|
||||
. $item['uri']
|
||||
. '">'
|
||||
. $item['title']
|
||||
. '</a>';
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $metadata['thumbnailUri']
|
||||
. '" /></a><br><a href="'
|
||||
. $item['uri']
|
||||
. '">'
|
||||
. $item['title']
|
||||
. '</a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
$count++;
|
||||
|
||||
if (count($this->items) >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
$specific = $this->getInput('u');
|
||||
break;
|
||||
case 'By playlist id':
|
||||
$specific = strtok($this->getInput('p'), '_');
|
||||
|
||||
if ($this->feedName) {
|
||||
$specific = $this->feedName;
|
||||
}
|
||||
|
||||
break;
|
||||
case 'From search results':
|
||||
$specific = $this->getInput('s');
|
||||
@@ -98,26 +125,76 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
default: return parent::getName();
|
||||
}
|
||||
|
||||
return $specific . ' : Dailymotion Bridge';
|
||||
return $specific . ' : Dailymotion';
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
$uri = self::URI;
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
$uri .= 'user/' . urlencode($this->getInput('u')) . '/1';
|
||||
$uri .= 'user/' . urlencode($this->getInput('u'));
|
||||
break;
|
||||
case 'By playlist id':
|
||||
$uri .= 'playlist/' . urlencode(strtok($this->getInput('p'), '_'));
|
||||
break;
|
||||
case 'From search results':
|
||||
$uri .= 'search/' . urlencode($this->getInput('s'));
|
||||
if($this->getInput('pa')) {
|
||||
$uri .= '/' . $this->getInput('pa');
|
||||
|
||||
if(!is_null($this->getInput('pa'))) {
|
||||
$pa = $this->getInput('pa');
|
||||
|
||||
if ($this->getInput('pa') < 1) {
|
||||
$pa = 1;
|
||||
}
|
||||
|
||||
$uri .= '/' . $pa;
|
||||
}
|
||||
break;
|
||||
default: return parent::getURI();
|
||||
}
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function getMetadata($id) {
|
||||
$metadata = array();
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . 'video/' . $id);
|
||||
|
||||
if(!$html) {
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
$metadata['title'] = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
$metadata['timestamp'] = strtotime(
|
||||
$html->find('meta[property=video:release_date]', 0)->getAttribute('content')
|
||||
);
|
||||
$metadata['thumbnailUri'] = $html->find('meta[property=og:image]', 0)->getAttribute('content');
|
||||
$metadata['uri'] = $html->find('meta[property=og:url]', 0)->getAttribute('content');
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
private function getPlaylistTitle($id) {
|
||||
$title = '';
|
||||
|
||||
$url = self::URI . 'playlist/' . $id;
|
||||
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
$title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
|
||||
return $title;
|
||||
}
|
||||
|
||||
private function getApiUrl() {
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'By username':
|
||||
return $this->apiUrl . '/user/' . $this->getInput('u')
|
||||
. '/videos?fields=' . urlencode($this->apiFields) . '&availability=1&sort=recent&limit=5';
|
||||
break;
|
||||
case 'By playlist id':
|
||||
return $this->apiUrl . '/playlist/' . $this->getInput('p')
|
||||
. '/videos?fields=' . urlencode($this->apiFields) . '&limit=5';
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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
|
||||
@@ -40,7 +40,7 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
defaultLinkTo($element, $this->getURI());
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['uri'] = html_entity_decode($element->find('a', 0)->href);
|
||||
$item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['timestamp'] = time();
|
||||
$thumbnailUri = $element->find('img', 0)->src;
|
||||
@@ -57,11 +57,79 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getFullURI())
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
$content = getContents($this->getFullURI());
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
@@ -9,8 +9,7 @@ class DansTonChatBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . 'latest.html')
|
||||
or returnServerError('Could not request DansTonChat.');
|
||||
$html = getSimpleHTMLDOM(self::URI . 'latest.html');
|
||||
|
||||
foreach($html->find('div.item') as $element) {
|
||||
$item = array();
|
||||
|
81
bridges/DarkReadingBridge.php
Normal file
81
bridges/DarkReadingBridge.php
Normal file
@@ -0,0 +1,81 @@
|
||||
<?php
|
||||
class DarkReadingBridge extends FeedExpander {
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'Dark Reading Bridge';
|
||||
const URI = 'https://www.darkreading.com/';
|
||||
const DESCRIPTION = 'Returns the newest articles from Dark Reading';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'feed' => array(
|
||||
'name' => 'Feed',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All Dark Reading Stories' => '000_AllArticles',
|
||||
'Attacks/Breaches' => '644_Attacks/Breaches',
|
||||
'Application Security' => '645_Application%20Security',
|
||||
'Database Security' => '646_Database%20Security',
|
||||
'Cloud' => '647_Cloud',
|
||||
'Endpoint' => '648_Endpoint',
|
||||
'Authentication' => '649_Authentication',
|
||||
'Privacy' => '650_Privacy',
|
||||
'Mobile' => '651_Mobile',
|
||||
'Perimeter' => '652_Perimeter',
|
||||
'Risk' => '653_Risk',
|
||||
'Compliance' => '654_Compliance',
|
||||
'Operations' => '655_Operations',
|
||||
'Careers and People' => '656_Careers%20and%20People',
|
||||
'Identity and Access Management' => '657_Identity%20and%20Access%20Management',
|
||||
'Analytics' => '658_Analytics',
|
||||
'Threat Intelligence' => '659_Threat%20Intelligence',
|
||||
'Security Monitoring' => '660_Security%20Monitoring',
|
||||
'Vulnerabilities / Threats' => '661_Vulnerabilities%20/%20Threats',
|
||||
'Advanced Threats' => '662_Advanced%20Threats',
|
||||
'Insider Threats' => '663_Insider%20Threats',
|
||||
'Vulnerability Management' => '664_Vulnerability%20Management',
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$feed = $this->getInput('feed');
|
||||
$feed_splitted = explode('_', $feed);
|
||||
$feed_id = $feed_splitted[0];
|
||||
$feed_name = $feed_splitted[1];
|
||||
if(empty($feed) || !ctype_digit($feed_id) || !preg_match('/[A-Za-z%20\/]/', $feed_name)) {
|
||||
returnClientError('Invalid feed, please check the "feed" parameter.');
|
||||
}
|
||||
$feed_url = $this->getURI() . 'rss_simple.asp';
|
||||
if ($feed_id != '000') {
|
||||
$feed_url .= '?f_n=' . $feed_id . '&f_ln=' . $feed_name;
|
||||
}
|
||||
$this->collectExpandableDatas($feed_url, 20);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
$article = getSimpleHTMLDOMCached($item['uri']);
|
||||
$item['content'] = $this->extractArticleContent($article);
|
||||
$item['enclosures'] = array(); //remove author profile picture
|
||||
$image = $article->find('meta[property="og:image"]', 0);
|
||||
if (is_object($image)) {
|
||||
$image = $image->content;
|
||||
$item['enclosures'] = array($image);
|
||||
}
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function extractArticleContent($article){
|
||||
$content = $article->find('div.article-content', 0)->innertext;
|
||||
|
||||
foreach (array(
|
||||
'<div class="divsplitter',
|
||||
'<div style="float: left; margin-right: 2px;',
|
||||
'<div class="more-insights',
|
||||
'<div id="more-insights',
|
||||
) as $div_start) {
|
||||
$content = stripRecursiveHTMLSection($content, 'div', $div_start);
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
23
bridges/DaveRamseyBlogBridge.php
Normal file
23
bridges/DaveRamseyBlogBridge.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
class DaveRamseyBlogBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'johnpc';
|
||||
const NAME = 'Dave Ramsey Blog';
|
||||
const URI = 'https://www.daveramsey.com/blog';
|
||||
const CACHE_TIMEOUT = 7200; // 2h
|
||||
const DESCRIPTION = 'Returns blog posts from daveramsey.com';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach ($html->find('.Post') as $element) {
|
||||
$this->items[] = array(
|
||||
'uri' => 'https://www.daveramsey.com' . $element->find('header > a', 0)->href,
|
||||
'title' => $element->find('header > h2 > a', 0)->plaintext,
|
||||
'tags' => $element->find('.Post-topic', 0)->plaintext,
|
||||
'content' => $element->find('.Post-body', 0)->plaintext,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
27
bridges/DavesTrailerPageBridge.php
Normal file
27
bridges/DavesTrailerPageBridge.php
Normal file
@@ -0,0 +1,27 @@
|
||||
<?php
|
||||
class DavesTrailerPageBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'johnnygroovy';
|
||||
const NAME = 'Daves Trailer Page Bridge';
|
||||
const URI = 'https://www.davestrailerpage.co.uk/';
|
||||
const DESCRIPTION = 'Last trailers in HD thanks to Dave.';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(static::URI)
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
foreach ($html->find('tr[!align]') as $tr) {
|
||||
$item = array();
|
||||
|
||||
// title
|
||||
$item['title'] = $tr->find('td', 0)->find('b', 0)->plaintext;
|
||||
|
||||
// content
|
||||
$item['content'] = $tr->find('ul', 1);
|
||||
|
||||
// uri
|
||||
$item['uri'] = $tr->find('a', 3)->getAttribute('href');
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@@ -1,166 +0,0 @@
|
||||
<?php
|
||||
class DemonoidBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'metaMMA';
|
||||
const NAME = 'Demonoid';
|
||||
const URI = 'https://www.demonoid.pw/';
|
||||
const DESCRIPTION = 'Returns results from search';
|
||||
|
||||
const PARAMETERS = array(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
|
||||
)
|
||||
)
|
||||
), 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
|
||||
)
|
||||
)
|
||||
), array(
|
||||
'userid' => array(
|
||||
'name' => 'user id',
|
||||
'exampleValue' => '00000',
|
||||
'required' => true,
|
||||
'type' => 'number'
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'All' => 0,
|
||||
'Movies' => 1,
|
||||
'Music' => 2,
|
||||
'TV' => 3,
|
||||
'Games' => 4,
|
||||
'Applications' => 5,
|
||||
'Pictures' => 8,
|
||||
'Anime' => 9,
|
||||
'Comics' => 10,
|
||||
'Books' => 11,
|
||||
'Audiobooks' => 17
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
if(!empty($this->getInput('q'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?category=' .
|
||||
rawurlencode($this->getInput('category')) .
|
||||
'&subcategory=All&quality=All&seeded=2&external=2&query=' .
|
||||
urlencode($this->getInput('q')) .
|
||||
'&uid=0&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} elseif(!empty($this->getInput('catOnly'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?uid=0&category=' .
|
||||
rawurlencode($this->getInput('catOnly')) .
|
||||
'&subcategory=0&language=0&seeded=2&quality=0&query=&sort='
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} elseif(!empty($this->getInput('userid'))) {
|
||||
|
||||
$html = getSimpleHTMLDOM(
|
||||
self::URI .
|
||||
'files/?uid=' .
|
||||
rawurlencode($this->getInput('userid')) .
|
||||
'&seeded=2'
|
||||
) or returnServerError('Could not request Demonoid.');
|
||||
|
||||
} else {
|
||||
returnServerError('Invalid parameters !');
|
||||
}
|
||||
|
||||
if(preg_match('~No torrents found~', $html)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$table = $html->find('td[class=ctable_content_no_pad]', 0);
|
||||
$cursorCount = 4;
|
||||
$elementCount = 0;
|
||||
while($elementCount != 40) {
|
||||
$elementCount++;
|
||||
$currentElement = $table->find('tr', $cursorCount);
|
||||
if(preg_match('~items total~', $currentElement)) {
|
||||
break;
|
||||
}
|
||||
$item = array();
|
||||
//Do we have a date ?
|
||||
if(preg_match('~Added.*?(.*)~', $currentElement->plaintext, $dateStr)) {
|
||||
if(preg_match('~today~', $dateStr[0])) {
|
||||
date_default_timezone_set('UTC');
|
||||
$timestamp = mktime(0, 0, 0, gmdate('n'), gmdate('j'), gmdate('Y'));
|
||||
} else {
|
||||
preg_match('~(?<=ed on ).*\d+~', $currentElement->plaintext, $fullDateStr);
|
||||
date_default_timezone_set('UTC');
|
||||
$dateObj = strptime($fullDateStr[0], '%A, %b %d, %Y');
|
||||
$timestamp = mktime(0, 0, 0, $dateObj['tm_mon'] + 1, $dateObj['tm_mday'], 1900 + $dateObj['tm_year']);
|
||||
}
|
||||
$cursorCount++;
|
||||
}
|
||||
|
||||
$content = $table->find('tr', $cursorCount)->find('a', 1);
|
||||
$cursorCount++;
|
||||
$torrentInfo = $table->find('tr', $cursorCount);
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['title'] = $content->plaintext;
|
||||
$item['id'] = self::URI . $content->href;
|
||||
$item['uri'] = self::URI . $content->href;
|
||||
$item['author'] = $torrentInfo->find('a[class=user]', 0)->plaintext;
|
||||
$item['seeders'] = $torrentInfo->find('font[class=green]', 0)->plaintext;
|
||||
$item['leechers'] = $torrentInfo->find('font[class=red]', 0)->plaintext;
|
||||
$item['size'] = $torrentInfo->find('td', 3)->plaintext;
|
||||
$item['content'] = 'Uploaded by ' . $item['author']
|
||||
. ' , Size ' . $item['size']
|
||||
. '<br>seeders: '
|
||||
. $item['seeders']
|
||||
. ' | leechers: '
|
||||
. $item['leechers']
|
||||
. '<br><a href="'
|
||||
. $item['id']
|
||||
. '">info page</a>';
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
$cursorCount++;
|
||||
}
|
||||
}
|
||||
}
|
115
bridges/DerpibooruBridge.php
Normal file
115
bridges/DerpibooruBridge.php
Normal file
@@ -0,0 +1,115 @@
|
||||
<?php
|
||||
class DerpibooruBridge extends BridgeAbstract {
|
||||
const NAME = 'Derpibooru Bridge';
|
||||
const URI = 'https://derpibooru.org/';
|
||||
const DESCRIPTION = 'Returns newest images 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
|
||||
. 'api/v1/json/search/images?filter_id='
|
||||
. urlencode($this->getInput('f'))
|
||||
. '&q='
|
||||
. urlencode($this->getInput('q'))
|
||||
));
|
||||
|
||||
foreach($queryJson->images as $post) {
|
||||
$item = array();
|
||||
|
||||
$postUri = self::URI . $post->id;
|
||||
|
||||
$item['uri'] = $postUri;
|
||||
$item['title'] = $post->name;
|
||||
$item['timestamp'] = strtotime($post->created_at);
|
||||
$item['author'] = $post->uploader;
|
||||
$item['enclosures'] = array($post->view_url);
|
||||
$item['categories'] = $post->tags;
|
||||
|
||||
$item['content'] = '<p><a href="' // image preview
|
||||
. $postUri
|
||||
. '"><img src="'
|
||||
. $post->representations->medium
|
||||
. '"></a></p><p>' // description
|
||||
. $post->description
|
||||
. '</p><p><b>Size:</b> ' // image size
|
||||
. $post->width
|
||||
. 'x'
|
||||
. $post->height;
|
||||
// source link
|
||||
if ($post->source_url != null) {
|
||||
$item['content'] .= '<br><b>Source:</b> <a href="'
|
||||
. $post->source_url
|
||||
. '">'
|
||||
. $post->source_url
|
||||
. '</a></p>';
|
||||
};
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
242
bridges/DesoutterBridge.php
Normal file
242
bridges/DesoutterBridge.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?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'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 3,
|
||||
'title' => "Maximum number of items to return in the feed.\n0 = unlimited"
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
$this->title = html_entity_decode($html->find('title', 0)->plaintext, ENT_QUOTES);
|
||||
|
||||
$limit = $this->getInput('limit') ?: 0;
|
||||
|
||||
foreach($html->find('article') as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('a', 0)->href;
|
||||
$item['title'] = $article->find('a[title]', 0)->title;
|
||||
|
||||
if($this->getInput('full')) {
|
||||
$item['content'] = $this->getFullNewsArticle($item['uri']);
|
||||
} else {
|
||||
$item['content'] = $article->find('div.tile-body p', 0)->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if ($limit > 0 && count($this->items) >= $limit) break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private function getFullNewsArticle($uri) {
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
|
||||
$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');
|
||||
|
||||
$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');
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
107
bridges/DevToBridge.php
Normal file
107
bridges/DevToBridge.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?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());
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
$articles = $html->find('div.crayons-story')
|
||||
or returnServerError('Could not find articles!');
|
||||
|
||||
foreach($articles as $article) {
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $article->find('a[id*=article-link]', 0)->href;
|
||||
$item['title'] = $article->find('h2 > a', 0)->plaintext;
|
||||
|
||||
$item['timestamp'] = $article->find('time', 0)->datetime;
|
||||
$item['author'] = $article->find('a.crayons-story__secondary.fw-medium', 0)->plaintext;
|
||||
|
||||
// Profile image
|
||||
$item['enclosures'] = array($article->find('img', 0)->src);
|
||||
|
||||
if($this->getInput('full')) {
|
||||
$fullArticle = $this->getFullArticle($item['uri']);
|
||||
$item['content'] = <<<EOD
|
||||
<p>{$fullArticle}</p>
|
||||
EOD;
|
||||
} else {
|
||||
$item['content'] = <<<EOD
|
||||
<img src="{$item['enclosures'][0]}" alt="{$item['author']}">
|
||||
<p>{$item['title']}</p>
|
||||
EOD;
|
||||
}
|
||||
|
||||
// categories
|
||||
foreach ($article->find('a.crayons-tag') as $tag) {
|
||||
$item['categories'][] = str_replace('#', '', $tag->plaintext);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if (!is_null($this->getInput('tag'))) {
|
||||
return ucfirst($this->getInput('tag')) . ' - dev.to';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getFullArticle($url) {
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
|
||||
$html = defaultLinkTo($html, static::URI);
|
||||
|
||||
if ($html->find('div.crayons-article__cover', 0)) {
|
||||
return $html->find('div.crayons-article__cover', 0) . $html->find('[id="article-body"]', 0);
|
||||
}
|
||||
|
||||
return $html->find('[id="article-body"]', 0);
|
||||
}
|
||||
}
|
83
bridges/DiarioDeNoticiasBridge.php
Normal file
83
bridges/DiarioDeNoticiasBridge.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
class DiarioDeNoticiasBridge extends BridgeAbstract {
|
||||
const NAME = 'Diário de Notícias (PT)';
|
||||
const URI = 'https://dn.pt';
|
||||
const DESCRIPTION = 'Diário de Notícias (DN.PT)';
|
||||
const MAINTAINER = 'somini';
|
||||
const PARAMETERS = array(
|
||||
'Tag' => array(
|
||||
'n' => array(
|
||||
'name' => 'Tag Name',
|
||||
'exampleValue' => 'rogerio-casanova',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const MONPT = array(
|
||||
'jan',
|
||||
'fev',
|
||||
'mar',
|
||||
'abr',
|
||||
'mai',
|
||||
'jun',
|
||||
'jul',
|
||||
'ago',
|
||||
'set',
|
||||
'out',
|
||||
'nov',
|
||||
'dez',
|
||||
);
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://static.globalnoticias.pt/dn/common/images/favicons/favicon-128.png';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Tag':
|
||||
$name = self::NAME . ' | Tag | ' . $this->getInput('n');
|
||||
break;
|
||||
default:
|
||||
$name = self::NAME;
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Tag':
|
||||
$url = self::URI . '/tag/' . $this->getInput('n') . '.html';
|
||||
break;
|
||||
default:
|
||||
$url = self::URI;
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$archives = self::getURI();
|
||||
$html = getSimpleHTMLDOMCached($archives);
|
||||
|
||||
foreach($html->find('article') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('.t-am-title', 0);
|
||||
$link = $element->find('a.t-am-text', 0);
|
||||
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['uri'] = self::URI . $link->href;
|
||||
|
||||
$snippet = $element->find('.t-am-lead', 0);
|
||||
if ($snippet) {
|
||||
$item['content'] = $snippet->plaintext;
|
||||
}
|
||||
preg_match('|edicao-do-dia\\/(?P<day>\d\d)-(?P<monpt>\w\w\w)-(?P<year>\d\d\d\d)|', $link->href, $d);
|
||||
if ($d) {
|
||||
$item['timestamp'] = sprintf('%s-%s-%s', $d['year'], array_search($d['monpt'], self::MONPT) + 1, $d['day']);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
59
bridges/DiarioDoAlentejoBridge.php
Normal file
59
bridges/DiarioDoAlentejoBridge.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
class DiarioDoAlentejoBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'somini';
|
||||
const NAME = 'Diário do Alentejo';
|
||||
const URI = 'https://www.diariodoalentejo.pt';
|
||||
const DESCRIPTION = 'Semanário Regionalista Independente';
|
||||
const CACHE_TIMEOUT = 28800; // 8h
|
||||
|
||||
/* This is used to hack around obtaining a timestamp. It's just a list of Month names in Portuguese ... */
|
||||
const PT_MONTH_NAMES = array(
|
||||
'janeiro',
|
||||
'fevereiro',
|
||||
'março',
|
||||
'abril',
|
||||
'maio',
|
||||
'junho',
|
||||
'julho',
|
||||
'agosto',
|
||||
'setembro',
|
||||
'outubro',
|
||||
'novembro',
|
||||
'dezembro');
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.diariodoalentejo.pt/images/favicon/apple-touch-icon.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
/* This is slow as molasses (>30s!), keep the cache timeout high to avoid killing the host */
|
||||
$html = getSimpleHTMLDOMCached($this->getURI() . '/pt/noticias-listagem.aspx');
|
||||
|
||||
foreach($html->find('.list_news .item') as $element) {
|
||||
$item = array();
|
||||
|
||||
$item_link = $element->find('.body h2.title a', 0);
|
||||
/* Another broken URL, see also `bridges/ComboiosDePortugalBridge.php` */
|
||||
$item['uri'] = self::URI . implode('/', array_map('urlencode', explode('/', $item_link->href)));
|
||||
$item['title'] = $item_link->innertext;
|
||||
|
||||
$item['timestamp'] = str_ireplace(
|
||||
array_map(function($name) { return ' ' . $name . ' '; }, self::PT_MONTH_NAMES),
|
||||
array_map(function($num) { return sprintf('-%02d-', $num); }, range(1, sizeof(self::PT_MONTH_NAMES))),
|
||||
$element->find('span.date', 0)->innertext);
|
||||
|
||||
/* Fix the Image URL */
|
||||
$item_image = $element->find('img.thumb', 0);
|
||||
$item_image->src = preg_replace('/.*&img=([^&]+).*/', '\1', $item_image->getAttribute('data-src'));
|
||||
|
||||
/* Content: */
|
||||
/* - Image */
|
||||
/* - Category */
|
||||
$content = $item_image .
|
||||
'<center>' . $element->find('a.category', 0) . '</center>';
|
||||
$item['content'] = defaultLinkTo($content, self::URI);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -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'));
|
||||
@@ -93,8 +97,7 @@ class DiceBridge extends BridgeAbstract {
|
||||
$uri .= '&telecommute=true';
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($uri)
|
||||
or returnServerError('Could not request Dice.');
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
foreach($html->find('div.complete-serp-result-div') as $element) {
|
||||
$item = array();
|
||||
// Title
|
||||
|
@@ -3,23 +3,22 @@ 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);
|
||||
|
||||
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);
|
||||
|
@@ -44,13 +44,11 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
if(!empty($this->getInput('artistid'))) {
|
||||
$data = getContents('https://api.discogs.com/artists/'
|
||||
. $this->getInput('artistid')
|
||||
. '/releases?sort=year&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/releases?sort=year&sort_order=desc');
|
||||
} elseif(!empty($this->getInput('labelid'))) {
|
||||
$data = getContents('https://api.discogs.com/labels/'
|
||||
. $this->getInput('labelid')
|
||||
. '/releases?sort=year&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/releases?sort=year&sort_order=desc');
|
||||
}
|
||||
|
||||
$jsonData = json_decode($data, true);
|
||||
@@ -62,7 +60,11 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
$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;
|
||||
$item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp();
|
||||
|
||||
if(isset($release['year'])) {
|
||||
$item['timestamp'] = DateTime::createFromFormat('Y', $release['year'])->getTimestamp();
|
||||
}
|
||||
|
||||
$item['content'] = $item['author'] . ' - ' . $item['title'];
|
||||
$this->items[] = $item;
|
||||
}
|
||||
@@ -72,8 +74,7 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
if(!empty($this->getInput('username_wantlist'))) {
|
||||
$data = getContents('https://api.discogs.com/users/'
|
||||
. $this->getInput('username_wantlist')
|
||||
. '/wants?sort=added&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/wants?sort=added&sort_order=desc');
|
||||
$jsonData = json_decode($data, true)['wants'];
|
||||
|
||||
} elseif(!empty($this->getInput('username_folder'))) {
|
||||
@@ -81,8 +82,7 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
. $this->getInput('username_folder')
|
||||
. '/collection/folders/'
|
||||
. $this->getInput('folderid')
|
||||
.'/releases?sort=added&sort_order=desc')
|
||||
or returnServerError('Unable to query discogs !');
|
||||
. '/releases?sort=added&sort_order=desc');
|
||||
$jsonData = json_decode($data, true)['releases'];
|
||||
}
|
||||
foreach($jsonData as $element) {
|
||||
|
165
bridges/DockerHubBridge.php
Normal file
165
bridges/DockerHubBridge.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
class DockerHubBridge extends BridgeAbstract {
|
||||
const NAME = 'Docker Hub Bridge';
|
||||
const URI = 'https://hub.docker.com';
|
||||
const DESCRIPTION = 'Returns new images for a container';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'User Submitted Image' => array(
|
||||
'user' => array(
|
||||
'name' => 'User',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rssbridge',
|
||||
),
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rss-bridge',
|
||||
)
|
||||
),
|
||||
'Official Image' => array(
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'postgres',
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
private $apiURL = 'https://hub.docker.com/v2/repositories/';
|
||||
private $imageUrlRegex = '/hub\.docker\.com\/r\/([\w]+)\/([\w-]+)\/?/';
|
||||
private $officialImageUrlRegex = '/hub\.docker\.com\/_\/([\w-]+)\/?/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// user submitted image
|
||||
if(preg_match($this->imageUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'User Submitted Image';
|
||||
$params['user'] = $matches[1];
|
||||
$params['repo'] = $matches[2];
|
||||
return $params;
|
||||
}
|
||||
|
||||
// official image
|
||||
if(preg_match($this->officialImageUrlRegex, $url, $matches)) {
|
||||
$params['context'] = 'Official Image';
|
||||
$params['repo'] = $matches[1];
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$json = getContents($this->getApiUrl());
|
||||
|
||||
$data = json_decode($json, false);
|
||||
|
||||
foreach ($data->results as $result) {
|
||||
$item = array();
|
||||
|
||||
$lastPushed = date('Y-m-d H:i:s', strtotime($result->tag_last_pushed));
|
||||
|
||||
$item['title'] = $result->name;
|
||||
$item['uid'] = $result->id;
|
||||
$item['uri'] = $this->getTagUrl($result->name);
|
||||
$item['author'] = $result->last_updater_username;
|
||||
$item['timestamp'] = $result->tag_last_pushed;
|
||||
$item['content'] = <<<EOD
|
||||
<Strong>Tag</strong><br>
|
||||
<p>{$result->name}</p>
|
||||
<Strong>Last pushed</strong><br>
|
||||
<p>{$lastPushed}</p>
|
||||
<Strong>Images</strong><br>
|
||||
{$this->getImages($result)}
|
||||
EOD;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/_/' . $this->getRepo();
|
||||
}
|
||||
|
||||
if ($this->getInput('repo')) {
|
||||
return self::URI . '/r/' . $this->getRepo();
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if ($this->getInput('repo')) {
|
||||
return $this->getRepo() . ' - Docker Hub';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getRepo() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return $this->getInput('repo');
|
||||
}
|
||||
|
||||
return $this->getInput('user') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
private function getApiUrl() {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return $this->apiURL . 'library/' . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||
}
|
||||
|
||||
return $this->apiURL . $this->getRepo() . '/tags/?page_size=25&page=1';
|
||||
}
|
||||
|
||||
private function getLayerUrl($name, $digest) {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/layers/' . $this->getRepo() . '/library/' .
|
||||
$this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||
}
|
||||
|
||||
return self::URI . '/layers/' . $this->getRepo() . '/' . $name . '/images/' . $digest;
|
||||
}
|
||||
|
||||
private function getTagUrl($name) {
|
||||
if ($this->queriedContext === 'Official Image') {
|
||||
return self::URI . '/_/' . $this->getRepo() . '?tab=tags&name=' . $name;
|
||||
}
|
||||
|
||||
return self::URI . '/r/' . $this->getRepo() . '/tags?name=' . $name;
|
||||
}
|
||||
|
||||
private function getImages($result) {
|
||||
$html = <<<EOD
|
||||
<table style="width:300px;"><thead><tr><th>Digest</th><th>OS/architecture</th></tr></thead></tbody>
|
||||
EOD;
|
||||
|
||||
foreach ($result->images as $image) {
|
||||
$layersUrl = $this->getLayerUrl($result->name, $image->digest);
|
||||
$id = $this->getShortDigestId($image->digest);
|
||||
|
||||
$html .= <<<EOD
|
||||
<tr>
|
||||
<td><a href="{$layersUrl}">{$id}</a></td>
|
||||
<td>{$image->os}/{$image->architecture}</td>
|
||||
</tr>
|
||||
EOD;
|
||||
}
|
||||
|
||||
return $html . '</tbody></table>';
|
||||
}
|
||||
|
||||
private function getShortDigestId($digest) {
|
||||
$parts = explode(':', $digest);
|
||||
return substr($parts[1], 0, 12);
|
||||
}
|
||||
}
|
@@ -1,9 +0,0 @@
|
||||
<?php
|
||||
require_once('Shimmie2Bridge.php');
|
||||
|
||||
class DollbooruBridge extends Shimmie2Bridge {
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Dollbooru';
|
||||
const URI = 'http://dollbooru.org/';
|
||||
const DESCRIPTION = 'Returns images from given page';
|
||||
}
|
122
bridges/DonnonsBridge.php
Normal file
122
bridges/DonnonsBridge.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* Retourne les dons d'une recherche filtrée sur le site Donnons.org
|
||||
* Example: https://donnons.org/Sport/Ile-de-France
|
||||
*/
|
||||
class DonnonsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Binnette';
|
||||
const NAME = 'Donnons.org';
|
||||
const URI = 'https://donnons.org';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Retourne les dons depuis le site Donnons.org.';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'q' => array(
|
||||
'name' => 'Url de recherche',
|
||||
'required' => true,
|
||||
'exampleValue' => '/Sport/Ile-de-France',
|
||||
'pattern' => '\/.*',
|
||||
'title' => 'Faites une recherche sur le site. Puis copiez ici la fin de l’url. Doit commencer par /',
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Nombre de pages à scanner',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 5,
|
||||
'title' => 'Indique le nombre de pages de donnons.org qui seront scannées'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$pages = $this->getInput('p');
|
||||
|
||||
for($i = 1; $i <= $pages; $i++) {
|
||||
$this->collectDataByPage($i);
|
||||
}
|
||||
}
|
||||
|
||||
private function collectDataByPage($page) {
|
||||
$uri = $this->getPageURI($page);
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
$searchDiv = $html->find('div[id=search]', 0);
|
||||
|
||||
if(!is_null($searchDiv)) {
|
||||
$elements = $searchDiv->find('a.lst-annonce');
|
||||
foreach($elements as $element) {
|
||||
$item = array();
|
||||
|
||||
// Lien vers le don
|
||||
$item['uri'] = self::URI . $element->href;
|
||||
// Id de l'objet
|
||||
$item['uid'] = $element->getAttribute('data-id');
|
||||
|
||||
// Grab info from json
|
||||
$jsonString = $element->find('script', 0)->innertext;
|
||||
$json = json_decode($jsonString, true);
|
||||
|
||||
$name = $json['name'];
|
||||
$category = $json['category'];
|
||||
$date = $json['availabilityStarts'];
|
||||
$description = $json['description'];
|
||||
$city = $json['availableAtOrFrom']['address']['addressLocality'];
|
||||
$region = $json['availableAtOrFrom']['address']['addressRegion'];
|
||||
|
||||
// Grab info from HTML
|
||||
$imageSrc = $element->find('img.ima-center', 0)->getAttribute('data-src');
|
||||
$image = self::URI . $imageSrc;
|
||||
$author = $element->find('div.avatar-holder', 0)->plaintext;
|
||||
|
||||
$content = '
|
||||
<img style="margin-right:1em;" src="' . $image . '">
|
||||
<div>
|
||||
<h1>' . $name . '</h1>
|
||||
<p>' . $description . '</p>
|
||||
<p>Lieu : <b>' . $city . '</b> - ' . $region . '</p>
|
||||
<p>Par : ' . $author . '</p>
|
||||
<p>Date : ' . $date . '</p>
|
||||
</div>
|
||||
';
|
||||
|
||||
// Titre du don
|
||||
$item['title'] = '[' . $category . '] ' . $name;
|
||||
$item['timestamp'] = $date;
|
||||
$item['author'] = $author;
|
||||
$item['content'] = $content;
|
||||
$item['enclosures'] = array($image);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function getPageURI($page) {
|
||||
$uri = $this->getURI();
|
||||
$haveQueryParams = strpos($uri, '?') !== false;
|
||||
|
||||
if($haveQueryParams) {
|
||||
return $uri . '&page=' . $page;
|
||||
} else {
|
||||
return $uri . '?page=' . $page;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return self::URI . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if(!is_null($this->getInput('q'))) {
|
||||
return 'Donnons.org - ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
194
bridges/DownDetectorBridge.php
Normal file
194
bridges/DownDetectorBridge.php
Normal file
@@ -0,0 +1,194 @@
|
||||
<?php
|
||||
class DownDetectorBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'DownDetector Bridge';
|
||||
const URI = 'https://downdetector.com/';
|
||||
const DESCRIPTION = 'Returns most recent downtimes from DownDetector';
|
||||
const CACHE_TIMEOUT = 300; // 5 min
|
||||
|
||||
const PARAMETERS = array(
|
||||
'All Websites' => array(
|
||||
'country' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Country',
|
||||
'values' => array(
|
||||
'Argentina' => 'https://downdetector.com.ar',
|
||||
'Australia' => 'https://downdetector.com.au',
|
||||
'België' => 'https://allestoringen.be',
|
||||
'Brasil' => 'https://downdetector.com.br',
|
||||
'Canada' => 'https://downdetector.ca',
|
||||
'Chile' => 'https://downdetector.cl',
|
||||
'Colombia' => 'https://downdetector.com.co',
|
||||
'Danmark' => 'https://downdetector.dk',
|
||||
'Deutschland' => 'https://allestörungen.de',
|
||||
'Ecuador' => 'https://downdetector.ec',
|
||||
'España' => 'https://downdetector.es',
|
||||
'France' => 'https://downdetector.fr',
|
||||
'Hong Kong' => 'https://downdetector.hk',
|
||||
'Hrvatska' => 'https://downdetector.hr',
|
||||
'India' => 'https://downdetector.in',
|
||||
'Indonesia' => 'https://downdetector.id',
|
||||
'Ireland' => 'https://downdetector.ie',
|
||||
'Italia' => 'https://downdetector.it',
|
||||
'Magyarország' => 'https://downdetector.hu',
|
||||
'Malaysia' => 'https://downdetector.my',
|
||||
'México' => 'https://downdetector.mx',
|
||||
'Nederland' => 'https://allestoringen.nl',
|
||||
'New Zealand' => 'https://downdetector.co.nz',
|
||||
'Norge' => 'https://downdetector.no',
|
||||
'Pakistan' => 'https://downdetector.pk',
|
||||
'Perú' => 'https://downdetector.pe',
|
||||
'Pilipinas' => 'https://downdetector.ph',
|
||||
'Polska' => 'https://downdetector.pl',
|
||||
'Portugal' => 'https://downdetector.pt',
|
||||
'România' => 'https://downdetector.ro',
|
||||
'Schweiz' => 'https://allestörungen.ch',
|
||||
'Singapore' => 'https://downdetector.sg',
|
||||
'Slovensko' => 'https://downdetector.sk',
|
||||
'South Africa' => 'https://downdetector.co.za',
|
||||
'Suomi' => 'https://downdetector.fi',
|
||||
'Sverige' => 'https://downdetector.se',
|
||||
'Türkiye' => 'https://downdetector.web.tr',
|
||||
'UAE' => 'https://downdetector.ae',
|
||||
'UK' => 'https://downdetector.co.uk',
|
||||
'United States' => 'https://downdetector.com',
|
||||
'Österreich' => 'https://allestörungen.at',
|
||||
'Česko' => 'https://downdetector.cz',
|
||||
'Ελλάς' => 'https://downdetector.gr',
|
||||
'Россия' => 'https://downdetector.ru',
|
||||
'日本' => 'https://downdetector.jp'
|
||||
)
|
||||
)
|
||||
),
|
||||
'Specific Website' => array(
|
||||
'page' => array(
|
||||
'type' => 'text',
|
||||
'name' => 'Status page',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://downdetector.com/status/rainbow-six',
|
||||
'title' => 'URL of a DownDetector status page e.g: https://downdetector.com/status/rainbow-six/',
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
private $hostname = '';
|
||||
private $statusPageId = '';
|
||||
private $feedname = '';
|
||||
|
||||
private $statusUrlRegex = '/\/([a-zA-z0-9ö.]+)\/(?:statu(?:s|t)|problemas?|nu-merge
|
||||
|(?:feil-)?problem(y|i)?(?:-storningar)?(?:-fejl)?|stoerung|durum|storing|fora-do-ar|ne-rabotaet
|
||||
|masalah|shougai|ei-toimi)\/([a-zA-Z0-9-]+)/';
|
||||
|
||||
public function collectData(){
|
||||
if ($this->queriedContext == 'Specific Website') {
|
||||
preg_match($this->statusUrlRegex, $this->getInput('page'), $match)
|
||||
or returnClientError('Given URL does not seem to at a DownDetector status page!');
|
||||
|
||||
$this->hostname = $match[1];
|
||||
$this->statusPageId = $match[3];
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/archive/')
|
||||
or returnClientError('Could not request website!.');
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
$this->feedname = $html->find('li.breadcrumb-item.active', 0)->plaintext;
|
||||
}
|
||||
|
||||
$table = $html->find('table.table-striped', 0);
|
||||
|
||||
if ($table) {
|
||||
foreach ($table->find('tr') as $event) {
|
||||
$td = $event->find('td', 0);
|
||||
|
||||
if (is_null($td)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['uri'] = $event->find('td', 0)->find('a', 0)->href;
|
||||
$item['title'] = $event->find('td', 0)->find('a', 0)->plaintext .
|
||||
'(' . trim($event->find('td', 1)->plaintext) . ' ' . trim($event->find('td', 2)->plaintext) . ')';
|
||||
$item['content'] = 'User reports indicate problems at' . $event->find('td', 0)->find('a', 0)->plaintext .
|
||||
' since ' . $event->find('td', 2)->plaintext;
|
||||
$item['timestamp'] = $this->formatDate(
|
||||
trim($event->find('td', 1)->plaintext),
|
||||
trim($event->find('td', 2)->plaintext)
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if($this->getInput('country')) {
|
||||
return $this->getInput('country');
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
return 'https://' . $this->hostname . '/status/' . $this->statusPageId;
|
||||
}
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if($this->getInput('country')) {
|
||||
$country = $this->getCountry($this->getInput('country'));
|
||||
return $country . ' - DownDetector';
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
$country = $this->getCountry($this->hostname);
|
||||
return $this->feedname . ' - ' . $country . ' - DownDetector';
|
||||
}
|
||||
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
private function formatDate($date, $time) {
|
||||
switch($this->getCountry()) {
|
||||
case 'Australia':
|
||||
case 'UK':
|
||||
$date = DateTime::createFromFormat('d/m/Y', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
case 'Brasil':
|
||||
case 'Chile':
|
||||
case 'Colombia':
|
||||
case 'Ecuador':
|
||||
case 'España':
|
||||
case 'Italia':
|
||||
case 'Perú':
|
||||
case 'Portugal':
|
||||
$date = DateTime::createFromFormat('d/m/Y', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
case 'Magyarország':
|
||||
$date = DateTime::createFromFormat('Y.m.d.', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
default:
|
||||
return $date . $time;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCountry() {
|
||||
if($this->getInput('country')) {
|
||||
$input = $this->getInput('country');
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
if (empty($this->hostname)) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
$input = 'https://' . $this->hostname;
|
||||
}
|
||||
|
||||
$parameters = $this->getParameters();
|
||||
$countryValues = array_flip($parameters['All Websites']['country']['values']);
|
||||
$country = $countryValues[$input];
|
||||
|
||||
return $country;
|
||||
}
|
||||
}
|
@@ -7,49 +7,56 @@ 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');
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$json = $this->loadEmbeddedJsonData($html);
|
||||
|
||||
foreach($html->find('li[id^="screenshot-"]') as $shot) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$additional_data = $this->findJsonForShot($shot, $json);
|
||||
if ($additional_data === null) {
|
||||
$item['uri'] = self::URI . $shot->find('a', 0)->href;
|
||||
$item['title'] = $shot->find('.dribbble-over strong', 0)->plaintext;
|
||||
$item['title'] = $shot->find('.shot-title', 0)->plaintext;
|
||||
} else {
|
||||
$item['timestamp'] = strtotime($additional_data['published_at']);
|
||||
$item['uri'] = self::URI . $additional_data['path'];
|
||||
$item['title'] = $additional_data['title'];
|
||||
}
|
||||
|
||||
$item['author'] = trim($shot->find('.attribution-user a', 0)->plaintext);
|
||||
$item['author'] = trim($shot->find('.user-information .display-name', 0)->plaintext);
|
||||
|
||||
$description = $shot->find('.comment', 0);
|
||||
$item['content'] = $description === null ? '' : $description->plaintext;
|
||||
|
||||
$preview_path = $shot->find('picture source', 0)->attr['srcset'];
|
||||
$preview_path = $shot->find('figure img', 1)->attr['data-srcset'];
|
||||
$item['content'] .= $this->getImageTag($preview_path, $item['title']);
|
||||
$item['enclosures'] = [$this->getFullSizeImagePath($preview_path)];
|
||||
$item['enclosures'] = array($this->getFullSizeImagePath($preview_path));
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function loadEmbeddedJsonData($html){
|
||||
$json = [];
|
||||
$json = array();
|
||||
$scripts = $html->find('script');
|
||||
|
||||
foreach($scripts as $script) {
|
||||
if(strpos($script->innertext, 'newestShots') !== false) {
|
||||
// fix single quotes
|
||||
$script->innertext = str_replace('\'', '"', $script->innertext);
|
||||
$script->innertext = preg_replace('/\'(.*)\'(,?)$/im', '"\1"\2', $script->innertext);
|
||||
|
||||
// fix JavaScript JSON (why do they not adhere to the standard?)
|
||||
$script->innertext = preg_replace('/(\w+):/i', '"\1":', $script->innertext);
|
||||
$script->innertext = preg_replace('/^(\s*)(\w+):/im', '\1"\2":', $script->innertext);
|
||||
|
||||
// fix relative dates, so they are recognized by strtotime
|
||||
$script->innertext = preg_replace('/"about ([0-9]+ hours? ago)"(,?)$/im', '"\1"\2', $script->innertext);
|
||||
|
||||
// find beginning of JSON array
|
||||
$start = strpos($script->innertext, '[');
|
||||
@@ -78,7 +85,7 @@ class DribbbleBridge extends BridgeAbstract {
|
||||
|
||||
private function getImageTag($preview_path, $title){
|
||||
return sprintf(
|
||||
'<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
|
||||
'<br /> <a href="%s"><img srcset="%s" alt="%s" /></a>',
|
||||
$this->getFullSizeImagePath($preview_path),
|
||||
$preview_path,
|
||||
$title
|
||||
@@ -86,6 +93,11 @@ class DribbbleBridge extends BridgeAbstract {
|
||||
}
|
||||
|
||||
private function getFullSizeImagePath($preview_path){
|
||||
return str_replace('_1x', '', $preview_path);
|
||||
// Get last image from srcset
|
||||
$src_set_urls = explode(',', $preview_path);
|
||||
$url = end($src_set_urls);
|
||||
$url = explode(' ', $url)[1];
|
||||
|
||||
return htmlspecialchars_decode($url);
|
||||
}
|
||||
}
|
||||
|
205
bridges/Drive2ruBridge.php
Normal file
205
bridges/Drive2ruBridge.php
Normal file
@@ -0,0 +1,205 @@
|
||||
<?php
|
||||
class Drive2ruBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'dotter-ak';
|
||||
const NAME = 'Drive2.ru';
|
||||
const URI = 'https://drive2.ru/';
|
||||
const DESCRIPTION = 'Лента новостей и тестдрайвов, бортжурналов по выбранной марке или модели
|
||||
(также работает с фильтром по категориям), блогов пользователей и публикаций по темам.';
|
||||
const PARAMETERS = array(
|
||||
'Новости и тест-драйвы' => array(),
|
||||
'Бортжурналы (По модели или марке)' => array(
|
||||
'url' => array(
|
||||
'name' => 'Ссылка на страницу с бортжурналом',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Например: https://www.drive2.ru/experience/suzuki/g4895/',
|
||||
'exampleValue' => 'https://www.drive2.ru/experience/suzuki/g4895/'
|
||||
),
|
||||
),
|
||||
'Личные блоги' => array(
|
||||
'username' => array(
|
||||
'name' => 'Никнейм пользователя на сайте',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Например: Mickey',
|
||||
'exampleValue' => 'Mickey'
|
||||
)
|
||||
),
|
||||
'Публикации по темам (Стоит почитать)' => array(
|
||||
'topic' => array(
|
||||
'name' => 'Темы',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Автозвук' => '16',
|
||||
'Автомобильный дизайн' => '10',
|
||||
'Автоспорт' => '11',
|
||||
'Автошоу, музеи, выставки' => '12',
|
||||
'Безопасность' => '18',
|
||||
'Беспилотные автомобили' => '15',
|
||||
'Видеосюжеты' => '20',
|
||||
'Вне дорог' => '21',
|
||||
'Встречи' => '22',
|
||||
'Выбор и покупка машины' => '23',
|
||||
'Гаджеты' => '30',
|
||||
'Гибридные машины' => '32',
|
||||
'Грузовики, автобусы, спецтехника' => '31',
|
||||
'Доработка интерьера' => '35',
|
||||
'Законодательство' => '40',
|
||||
'История автомобилестроения' => '50',
|
||||
'Мототехника' => '60',
|
||||
'Новые модели и концепты' => '85',
|
||||
'Обучение вождению' => '70',
|
||||
'Путешествия' => '80',
|
||||
'Ремонт и обслуживание' => '90',
|
||||
'Реставрация ретро-авто' => '91',
|
||||
'Сделай сам' => '104',
|
||||
'Смешное' => '103',
|
||||
'Спорткары' => '102',
|
||||
'Стайлинг' => '101',
|
||||
'Тест-драйвы' => '110',
|
||||
'Тюнинг' => '111',
|
||||
'Фотосессии' => '120',
|
||||
'Шины и диски' => '140',
|
||||
'Электрика' => '130',
|
||||
'Электромобили' => '131'
|
||||
),
|
||||
'defaultValue' => '16',
|
||||
)
|
||||
),
|
||||
'global' => array(
|
||||
'full_articles' => array(
|
||||
'name' => 'Загружать в ленту полный текст',
|
||||
'type' => 'checkbox'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private $title;
|
||||
|
||||
private function getUserContent($url) {
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
$articles = $html->find('div.js-entity');
|
||||
foreach ($articles as $article) {
|
||||
$item = array();
|
||||
$item['title'] = $article->find('a.c-link--text', 0)->plaintext;
|
||||
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 0)->href);
|
||||
if($this->getInput('full_articles')) {
|
||||
$item['content'] = $this->addCommentsLink(
|
||||
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.c-post__body', 0))->innertext,
|
||||
$item['uri']
|
||||
);
|
||||
} else {
|
||||
$item['content'] = $this->addReadMoreLink($article->find('div.c-post-preview__lead', 0), $item['uri']);
|
||||
}
|
||||
$item['author'] = $article->find('a.c-username--wrap', 0)->plaintext;
|
||||
if (!is_null($article->find('img', 1))) $item['enclosures'][] = $article->find('img', 1)->src;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getLogbooksContent($url) {
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
$articles = $html->find('div.js-entity');
|
||||
foreach ($articles as $article) {
|
||||
$item = array();
|
||||
$item['title'] = $article->find('a.c-link--text', 1)->plaintext;
|
||||
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 1)->href);
|
||||
if($this->getInput('full_articles')) {
|
||||
$item['content'] = $this->addCommentsLink(
|
||||
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.c-post__body', 0))->innertext,
|
||||
$item['uri']
|
||||
);
|
||||
} else {
|
||||
$item['content'] = $this->addReadMoreLink($article->find('div.c-post-preview__lead', 0), $item['uri']);
|
||||
}
|
||||
$item['author'] = $article->find('a.c-username--wrap', 0)->plaintext;
|
||||
if (!is_null($article->find('img', 1))) $item['enclosures'][] = $article->find('img', 1)->src;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getNews() {
|
||||
$html = getSimpleHTMLDOM('https://www.drive2.ru/editorial/');
|
||||
$this->title = $html->find('title', 0)->innertext;
|
||||
$articles = $html->find('div.c-article-card');
|
||||
foreach ($articles as $article) {
|
||||
$item = array();
|
||||
$item['title'] = $article->find('a.c-link--text', 0)->plaintext;
|
||||
$item['uri'] = urljoin(self::URI, $article->find('a.c-link--text', 0)->href);
|
||||
if($this->getInput('full_articles')) {
|
||||
$item['content'] = $this->addCommentsLink(
|
||||
$this->adjustContent(getSimpleHTMLDomCached($item['uri'])->find('div.article', 0))->innertext,
|
||||
$item['uri']
|
||||
);
|
||||
} else {
|
||||
$item['content'] = $this->addReadMoreLink($article->find('div.c-article-card__lead', 0), $item['uri']);
|
||||
}
|
||||
$item['author'] = 'Новости и тест-драйвы на Drive2.ru';
|
||||
if (!is_null($article->find('img', 0))) $item['enclosures'][] = $article->find('img', 0)->src;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function adjustContent($content) {
|
||||
foreach ($content->find('div.o-group') as $node)
|
||||
$node->outertext = '';
|
||||
foreach($content->find('div, span') as $attrs)
|
||||
foreach ($attrs->getAllAttributes() as $attr => $val)
|
||||
$attrs->removeAttribute($attr);
|
||||
foreach ($content->getElementsByTagName('figcaption') as $attrs)
|
||||
$attrs->setAttribute(
|
||||
'style',
|
||||
'font-style: italic; font-size: small; margin: 0 100px 75px;');
|
||||
foreach ($content->find('script') as $node)
|
||||
$node->outertext = '';
|
||||
foreach ($content->find('iframe') as $node) {
|
||||
preg_match('/embed\/(.*?)\?/', $node->src, $match);
|
||||
$node->outertext = '<a href="https://www.youtube.com/watch?v=' . $match[1] .
|
||||
'">https://www.youtube.com/watch?v=' . $match[1] . '</a>';
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
private function addCommentsLink ($content, $url) {
|
||||
return $content . '<br><a href="' . $url . '#comments">Перейти к комментариям</a>';
|
||||
}
|
||||
|
||||
private function addReadMoreLink ($content, $url) {
|
||||
if (!is_null($content))
|
||||
return preg_replace('!\s+!', ' ', str_replace('Читать дальше', '', $content->plaintext)) .
|
||||
'<br><a href="' . $url . '">Читать далее</a>';
|
||||
else return '';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
default:
|
||||
case 'Новости и тест-драйвы':
|
||||
$this->getNews();
|
||||
break;
|
||||
case 'Бортжурналы (По модели или марке)':
|
||||
if (!preg_match('/^https:\/\/www.drive2.ru\/experience/', $this->getInput('url')))
|
||||
returnServerError('Invalid url');
|
||||
$this->getLogbooksContent($this->getInput('url'));
|
||||
break;
|
||||
case 'Личные блоги':
|
||||
if (!preg_match('/^[a-zA-Z0-9-]{3,16}$/', $this->getInput('username')))
|
||||
returnServerError('Invalid username');
|
||||
$this->getUserContent('https://www.drive2.ru/users/' . $this->getInput('username'));
|
||||
break;
|
||||
case 'Публикации по темам (Стоит почитать)':
|
||||
$this->getUserContent('https://www.drive2.ru/topics/' . $this->getInput('topic'));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->title ?: parent::getName();
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.drive2.ru/favicon.ico';
|
||||
}
|
||||
}
|
@@ -28,14 +28,13 @@ class DuckDuckGoBridge extends BridgeAbstract {
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'html/?kd=-1&q=' . $this->getInput('u') . $this->getInput('sort'))
|
||||
or returnServerError('Could not request DuckDuckGo.');
|
||||
$html = getSimpleHTMLDOM(self::URI . 'html/?kd=-1&q=' . $this->getInput('u') . $this->getInput('sort'));
|
||||
|
||||
foreach($html->find('div.results_links') as $element) {
|
||||
foreach($html->find('div.result') as $element) {
|
||||
$item = array();
|
||||
$item['uri'] = $element->find('a', 0)->href;
|
||||
$item['title'] = $element->find('a', 1)->innertext;
|
||||
$item['content'] = $element->find('div.snippet', 0)->plaintext;
|
||||
$item['uri'] = $element->find('a.result__a', 0)->href;
|
||||
$item['title'] = $element->find('h2.result__title', 0)->plaintext;
|
||||
$item['content'] = $element->find('a.result__snippet', 0)->plaintext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -94,18 +94,20 @@ class ETTVBridge extends BridgeAbstract {
|
||||
)
|
||||
));
|
||||
|
||||
protected $results_link;
|
||||
|
||||
public function collectData(){
|
||||
// No control on inputs, because all have defaultValue set
|
||||
// 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 .= '?search=' . urlencode('+' . str_replace(' ', ' +', $this->getInput('query')));
|
||||
$query_str .= '&cat=' . $this->getInput('cat');
|
||||
$query_str .= 'incldead&=' . $this->getInput('status');
|
||||
$query_str .= '&incldead=' . $this->getInput('status');
|
||||
$query_str .= '&lang=' . $this->getInput('lang');
|
||||
$query_str .= '&sort=id&order=desc';
|
||||
|
||||
// Get results page
|
||||
$html = getSimpleHTMLDOM(self::URI . $query_str)
|
||||
or returnServerError('Could not request ' . $this->getName());
|
||||
$this->results_link = self::URI . $query_str;
|
||||
$html = getSimpleHTMLDOM($this->results_link);
|
||||
|
||||
// Loop on each entry
|
||||
foreach($html->find('table.table tr') as $element) {
|
||||
@@ -114,8 +116,7 @@ class ETTVBridge extends BridgeAbstract {
|
||||
|
||||
// retrieve result page to get more details
|
||||
$link = rtrim(self::URI, '/') . $entry->href;
|
||||
$page = getSimpleHTMLDOM($link)
|
||||
or returnServerError('Could not request page ' . $link);
|
||||
$page = getSimpleHTMLDOM($link);
|
||||
|
||||
// get details & download links
|
||||
$details = $page->find('fieldset.download table', 0); // WHAT?? It should be the second one…
|
||||
@@ -125,7 +126,7 @@ class ETTVBridge extends BridgeAbstract {
|
||||
$item = array();
|
||||
$item['author'] = $details->children(6)->children(1)->plaintext;
|
||||
$item['title'] = $entry->title;
|
||||
$item['uri'] = $dllinks->children(0)->children(0)->children(0)->href;
|
||||
$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;
|
||||
@@ -139,4 +140,20 @@ class ETTVBridge extends BridgeAbstract {
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user