mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-17 14:00:43 +02:00
Compare commits
347 Commits
2022-01-20
...
2022-06-14
Author | SHA1 | Date | |
---|---|---|---|
|
9503f9ad7f | ||
|
3e2423d86b | ||
|
90e0504da5 | ||
|
4b3b1ca163 | ||
|
5b93bba1a3 | ||
|
b8786da137 | ||
|
166ead902d | ||
|
de279de762 | ||
|
347f9a3eda | ||
|
1af6cbeb1e | ||
|
37f211a37e | ||
|
bea0595e5c | ||
|
6df5a4bc14 | ||
|
3927ecd822 | ||
|
1b0a6f2813 | ||
|
8f0d90f653 | ||
|
037d5866ca | ||
|
75c4c9f256 | ||
|
baa4ea8338 | ||
|
12ddee4054 | ||
|
6582a66a2d | ||
|
a4785370fa | ||
|
aa32040bd4 | ||
|
44e8007d9c | ||
|
90d22f0d80 | ||
|
fb501652d5 | ||
|
e85932b1a5 | ||
|
53f9970403 | ||
|
19ad2584da | ||
|
190c95fa62 | ||
|
678e5d9866 | ||
|
4787eb3799 | ||
|
a863234474 | ||
|
4260be26a2 | ||
|
713d06ba08 | ||
|
7256d1138b | ||
|
71310d2c5a | ||
|
92d813fbea | ||
|
3f896f9465 | ||
|
b7e1dc1ab1 | ||
|
8e41887393 | ||
|
8865521b3b | ||
|
8172d10bb5 | ||
|
299ad87168 | ||
|
d60d8313d0 | ||
|
1fd2f37bb4 | ||
|
04b1609ce0 | ||
|
2fa24e780b | ||
|
3b04e318ae | ||
|
05cd1c0b67 | ||
|
cb05cacd6a | ||
|
4d18312604 | ||
|
85e5ce2679 | ||
|
7afc577e97 | ||
|
462319344b | ||
|
5cc34b884a | ||
|
dd025894e9 | ||
|
1d0a0b927b | ||
|
4007afdcf5 | ||
|
7d00b0c5df | ||
|
0212c4790f | ||
|
7a87a09fc5 | ||
|
1e3f5f3ad3 | ||
|
f709778b28 | ||
|
f4a0711b62 | ||
|
4d069fcf99 | ||
|
f00f90328d | ||
|
bb6d553dd5 | ||
|
97b513823d | ||
|
e01f0bcaf2 | ||
|
e5829d37b6 | ||
|
73b1a6a7aa | ||
|
e07fac777a | ||
|
fd449be4eb | ||
|
829fc6cca2 | ||
|
fcc3707210 | ||
|
d5e9dbf47d | ||
|
96a63a8e81 | ||
|
9110b70f07 | ||
|
6547ed0c04 | ||
|
8982995445 | ||
|
76084cdcca | ||
|
a28dca2c9d | ||
|
51f0d046d0 | ||
|
fb2ed95368 | ||
|
36d11fd06e | ||
|
d107592094 | ||
|
0ce71d561d | ||
|
f5a51038cc | ||
|
3476b06ee0 | ||
|
158ee41be4 | ||
|
37843e8777 | ||
|
56e991122b | ||
|
5d77d14f9d | ||
|
0c7a7f320f | ||
|
b2f1d051fc | ||
|
641e2eedf5 | ||
|
bc773a49f8 | ||
|
410daee1d5 | ||
|
adeaede930 | ||
|
9b82ff352d | ||
|
31455b6838 | ||
|
63b08f7da9 | ||
|
61cfbe6c53 | ||
|
4c26950b71 | ||
|
9dc31dfcfa | ||
|
db8462e6fa | ||
|
19a8165fc6 | ||
|
0ef298f9cc | ||
|
b090b17bbf | ||
|
ca749e7bad | ||
|
e1c898848f | ||
|
46a356b0b2 | ||
|
fe042305e4 | ||
|
669e92357a | ||
|
1dec457b7b | ||
|
a38951b911 | ||
|
b11f1368bf | ||
|
1a698b3554 | ||
|
73ebdbf67a | ||
|
ac766aa47f | ||
|
2be613e015 | ||
|
924eaf2011 | ||
|
d082bfca4a | ||
|
91283f3a62 | ||
|
d6beb713b5 | ||
|
d62b977394 | ||
|
183004f954 | ||
|
ff8ece213f | ||
|
3e5675c256 | ||
|
5a7d305e07 | ||
|
7379e2b3d5 | ||
|
57c8806954 | ||
|
b6e8350596 | ||
|
5b7dd45b20 | ||
|
f9801a5c58 | ||
|
563c099d80 | ||
|
b6e8e3ea6e | ||
|
9e2e32a19d | ||
|
df5c259375 | ||
|
6021d2ffa6 | ||
|
908da78113 | ||
|
a28481aaa8 | ||
|
bb81af086f | ||
|
60f1c46779 | ||
|
ae760e40cc | ||
|
5a733b3d82 | ||
|
0b40f51c01 | ||
|
dbee47f1d6 | ||
|
c3a106892d | ||
|
db28bedb23 | ||
|
aacf5812ff | ||
|
bf2f9a06f9 | ||
|
7833d0e6c3 | ||
|
c498749c2b | ||
|
722f9ff0ce | ||
|
5c08984714 | ||
|
dc01891634 | ||
|
cce11964a4 | ||
|
8c18c02c65 | ||
|
51d27300be | ||
|
c0e2a430ab | ||
|
daae089299 | ||
|
d98add2cac | ||
|
a3b0b91dee | ||
|
6ffe531e4f | ||
|
fb28107cc4 | ||
|
2c50bbae95 | ||
|
8f9314947b | ||
|
b24cdd47f0 | ||
|
233a3cb643 | ||
|
91c6645fc7 | ||
|
780581939a | ||
|
0d305f1530 | ||
|
e1e9a12440 | ||
|
d34b94848b | ||
|
2eaf48de99 | ||
|
d3bb00f754 | ||
|
00a3f80ac4 | ||
|
260fc41d72 | ||
|
28f5066fc4 | ||
|
aa83a990d1 | ||
|
7dcf09a876 | ||
|
d123e6007e | ||
|
a5eb02d3c3 | ||
|
7b168a29f0 | ||
|
bed20e9f28 | ||
|
42788cd3ee | ||
|
fb0e7ede89 | ||
|
f311fb8083 | ||
|
40a4e7b7c2 | ||
|
73cc791ce1 | ||
|
d4707fc119 | ||
|
8aa091beda | ||
|
d6695c0e73 | ||
|
b6798b9878 | ||
|
6baf38f251 | ||
|
e6ae91b4d0 | ||
|
e525b5b427 | ||
|
983df45264 | ||
|
8717c33646 | ||
|
7280ed7df7 | ||
|
d6b431a34b | ||
|
aa0aa727ad | ||
|
06ef3946cd | ||
|
e94d447727 | ||
|
25e9f69261 | ||
|
3e363bbc20 | ||
|
cf2dad3ab8 | ||
|
d6a4f2fd5b | ||
|
d27c1a99c2 | ||
|
0d80f2d5c3 | ||
|
a485beadd7 | ||
|
ec7d2a4afb | ||
|
427becf441 | ||
|
267fdb27fc | ||
|
ac242609f4 | ||
|
461269195b | ||
|
060b4c7d58 | ||
|
cd174c7e22 | ||
|
907d09f116 | ||
|
c6675ddeee | ||
|
98a0c2de55 | ||
|
a746987d7a | ||
|
6d4155f995 | ||
|
58f9e41e0b | ||
|
e86ce338a2 | ||
|
626cc9119a | ||
|
44af64d3aa | ||
|
90db8c4969 | ||
|
8e423277e0 | ||
|
fe43537b45 | ||
|
87533222c7 | ||
|
91b8e4196e | ||
|
74ec1b5687 | ||
|
94e6feced2 | ||
|
b144ab2bd7 | ||
|
012ecf8e52 | ||
|
4d4ce3f380 | ||
|
2c00ecb923 | ||
|
02ba3adcc9 | ||
|
37e3d6f2f6 | ||
|
7f4a0fae0c | ||
|
33da1476c9 | ||
|
364cc8d0b8 | ||
|
4c947211d2 | ||
|
c46ff51c51 | ||
|
608723f95c | ||
|
25081eedba | ||
|
aff442de1b | ||
|
105fbe9dda | ||
|
3187592dba | ||
|
2bd3f22dd5 | ||
|
35b905c074 | ||
|
197149d90b | ||
|
f11e792f84 | ||
|
071412130b | ||
|
8b59772be3 | ||
|
6e0589f9a0 | ||
|
b57d19b29c | ||
|
dbd480e2c0 | ||
|
35afee6103 | ||
|
32a6348418 | ||
|
b5ab2ee676 | ||
|
acef0ab5cc | ||
|
e0d99f2a84 | ||
|
55acf661b9 | ||
|
3a9e528301 | ||
|
297a6cf191 | ||
|
9cd8e93bb9 | ||
|
943a5e3e8b | ||
|
2ade568a84 | ||
|
50bab079e1 | ||
|
bb06826680 | ||
|
534864f47b | ||
|
f7af2beb79 | ||
|
76ade41543 | ||
|
cb4bc57c72 | ||
|
5c69577253 | ||
|
78a5136cc9 | ||
|
1f2b295bf3 | ||
|
e89b4287b8 | ||
|
02ab11121b | ||
|
3d570761e5 | ||
|
1ae7cf6530 | ||
|
8e2b65556f | ||
|
0d20e9a05c | ||
|
6a72432f76 | ||
|
296ff9c63a | ||
|
2bba89d0f5 | ||
|
b1c36da14e | ||
|
1a8d0babd1 | ||
|
f766193106 | ||
|
b6d1c7a58f | ||
|
f34e09e93b | ||
|
384790537b | ||
|
7bdc53125c | ||
|
076c413d3e | ||
|
26f0380aaa | ||
|
14a7516625 | ||
|
c30c0200d5 | ||
|
e01d9d1700 | ||
|
d41aa84b13 | ||
|
6211a2cd37 | ||
|
76f5de3d0f | ||
|
16470e8119 | ||
|
1fd3b12512 | ||
|
5aa163e7d6 | ||
|
ec90bd905e | ||
|
b646afffff | ||
|
05c31f49ce | ||
|
0b123ef8be | ||
|
cd5c59b84c | ||
|
e8db2479b5 | ||
|
c87f4631f2 | ||
|
ac8e94ec56 | ||
|
1a3419a2d4 | ||
|
ad6549efec | ||
|
3638b5553a | ||
|
a7e70926f9 | ||
|
18504f2356 | ||
|
05273a9278 | ||
|
2e88955648 | ||
|
cbef3b3360 | ||
|
9564e9291f | ||
|
ad1ef3425a | ||
|
3bd4b0d6ab | ||
|
6585ebc89b | ||
|
d94bb08259 | ||
|
2811bdc054 | ||
|
0cf9da927e | ||
|
73a5dd928a | ||
|
680fa29668 | ||
|
765af484bc | ||
|
7252252e3c | ||
|
3c18784576 | ||
|
3cde07db10 | ||
|
8723647513 | ||
|
f54c996e0f | ||
|
09fac3aa35 | ||
|
c1c998dd13 | ||
|
fb19142a54 | ||
|
9be00ff84e | ||
|
918041cc28 | ||
|
e9f871ce68 | ||
|
018fd1c8f2 | ||
|
30553d8665 |
@@ -1,4 +1,6 @@
|
||||
.git
|
||||
!.git/HEAD
|
||||
!.git/refs/heads/*
|
||||
.gitattributes
|
||||
.github/*
|
||||
.travis.yml
|
||||
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,5 +1,6 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
*.sh text eol=lf
|
||||
|
||||
# Custom for Visual Studio
|
||||
*.cs diff=csharp
|
||||
|
46
.github/CONTRIBUTING.md
vendored
46
.github/CONTRIBUTING.md
vendored
@@ -1,49 +1,7 @@
|
||||
### 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.
|
||||
See the [Pull request policy page on the documentation](https://rss-bridge.github.io/rss-bridge/For_Developers/Pull_Request_policy.html) for more information on the pull request policy.
|
||||
|
||||
### 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)
|
||||
See the [Coding style policy page on the documentation](https://rss-bridge.github.io/rss-bridge/For_Developers/Coding_style_policy.html) for more information on the coding style of the project.
|
||||
|
2
.github/ISSUE_TEMPLATE/bridge-request.md
vendored
2
.github/ISSUE_TEMPLATE/bridge-request.md
vendored
@@ -60,5 +60,5 @@ Please describe what you expect from the bridge. Whenever possible provide sampl
|
||||
|
||||
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.
|
||||
You can also implement your own bridge (with support of the community if needed). Find more information in the [RSS-Bridge Documentation](https://rss-bridge.github.io/rss-bridge/For_Developers/index.html) developer section.
|
||||
-->
|
||||
|
2
.github/prtester-requirements.txt
vendored
Normal file
2
.github/prtester-requirements.txt
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
beautifulsoup4>=4.10.0
|
||||
requests>=2.26.0
|
105
.github/prtester.py
vendored
Normal file
105
.github/prtester.py
vendored
Normal file
@@ -0,0 +1,105 @@
|
||||
import requests
|
||||
import itertools
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
import os.path
|
||||
|
||||
# This script is specifically written to be used in automation for https://github.com/RSS-Bridge/rss-bridge
|
||||
#
|
||||
# This will scrape the whitelisted bridges in the current state (port 3000) and the PR state (port 3001) of
|
||||
# RSS-Bridge, generate a feed for each of the bridges and save the output as html files.
|
||||
# It also replaces the default static CSS link with a hardcoded link to @em92's public instance, so viewing
|
||||
# the HTML file locally will actually work as designed.
|
||||
|
||||
def testBridges(bridges,status):
|
||||
for bridge in bridges:
|
||||
if bridge.get('data-ref'): # Some div entries are empty, this ignores those
|
||||
bridgeid = bridge.get('id')
|
||||
bridgeid = bridgeid.split('-')[1] # this extracts a readable bridge name from the bridge metadata
|
||||
bridgestring = '/?action=display&bridge=' + bridgeid + '&format=Html'
|
||||
forms = bridge.find_all("form")
|
||||
formid = 1
|
||||
for form in forms:
|
||||
# a bridge can have multiple contexts, named 'forms' in html
|
||||
# this code will produce a fully working formstring that should create a working feed when called
|
||||
# this will create an example feed for every single context, to test them all
|
||||
formstring = ''
|
||||
errormessages = []
|
||||
parameters = form.find_all("input")
|
||||
lists = form.find_all("select")
|
||||
# this for/if mess cycles through all available input parameters, checks if it required, then pulls
|
||||
# the default or examplevalue and then combines it all together into the formstring
|
||||
# if an example or default value is missing for a required attribute, it will throw an error
|
||||
# any non-required fields are not tested!!!
|
||||
for parameter in parameters:
|
||||
if parameter.get('type') == 'hidden' and parameter.get('name') == 'context':
|
||||
cleanvalue = parameter.get('value').replace(" ","+")
|
||||
formstring = formstring + '&' + parameter.get('name') + '=' + cleanvalue
|
||||
if parameter.get('type') == 'number' or parameter.get('type') == 'text':
|
||||
if parameter.has_attr('required'):
|
||||
if parameter.get('placeholder') == '':
|
||||
if parameter.get('value') == '':
|
||||
errormessages.append(parameter.get('name'))
|
||||
else:
|
||||
formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('value')
|
||||
else:
|
||||
formstring = formstring + '&' + parameter.get('name') + '=' + parameter.get('placeholder')
|
||||
# same thing, just for checkboxes. If a checkbox is checked per default, it gets added to the formstring
|
||||
if parameter.get('type') == 'checkbox':
|
||||
if parameter.has_attr('checked'):
|
||||
formstring = formstring + '&' + parameter.get('name') + '=on'
|
||||
for listing in lists:
|
||||
selectionvalue = ''
|
||||
listname = listing.get('name')
|
||||
if 'optgroup' in listing.contents[0].name:
|
||||
listing = list(itertools.chain.from_iterable(listing))
|
||||
for selectionentry in listing:
|
||||
if 'selected' in selectionentry.attrs:
|
||||
selectionvalue = selectionentry.get('value')
|
||||
break
|
||||
if selectionvalue == '':
|
||||
selectionvalue = listing.contents[0].get('value')
|
||||
formstring = formstring + '&' + listname + '=' + selectionvalue
|
||||
if not errormessages:
|
||||
# if all example/default values are present, form the full request string, run the request, replace the static css
|
||||
# file with the url of em's public instance and then upload it to termpad.com, a pastebin-like-site.
|
||||
r = requests.get(URL + bridgestring + formstring)
|
||||
pagetext = r.text.replace('static/HtmlFormat.css','https://feed.eugenemolotov.ru/static/HtmlFormat.css')
|
||||
pagetext = pagetext.encode("utf_8")
|
||||
termpad = requests.post(url="https://termpad.com/", data=pagetext)
|
||||
termpadurl = termpad.text
|
||||
termpadurl = termpadurl.replace('termpad.com/','termpad.com/raw/')
|
||||
termpadurl = termpadurl.replace('\n','')
|
||||
with open(os.getcwd() + '/comment.txt', 'a+') as file:
|
||||
file.write("\n")
|
||||
file.write("| [`" + bridgeid + '-' + status + '-context' + str(formid) + "`](" + termpadurl + ") | " + date_time + " |")
|
||||
else:
|
||||
# if there are errors (which means that a required value has no example or default value), log out which error appeared
|
||||
termpad = requests.post(url="https://termpad.com/", data=str(errormessages))
|
||||
termpadurl = termpad.text
|
||||
termpadurl = termpadurl.replace('termpad.com/','termpad.com/raw/')
|
||||
termpadurl = termpadurl.replace('\n','')
|
||||
with open(os.getcwd() + '/comment.txt', 'a+') as file:
|
||||
file.write("\n")
|
||||
file.write("| [`" + bridgeid + '-' + status + '-context' + str(formid) + "`](" + termpadurl + ") | " + date_time + " |")
|
||||
formid += 1
|
||||
|
||||
gitstatus = ["current", "pr"]
|
||||
now = datetime.now()
|
||||
date_time = now.strftime("%Y-%m-%d, %H:%M:%S")
|
||||
|
||||
with open(os.getcwd() + '/comment.txt', 'w+') as file:
|
||||
file.write(''' ## Pull request artifacts
|
||||
| file | last change |
|
||||
| ---- | ------ |''')
|
||||
|
||||
for status in gitstatus: # run this twice, once for the current version, once for the PR version
|
||||
if status == "current":
|
||||
port = "3000" # both ports are defined in the corresponding workflow .yml file
|
||||
elif status == "pr":
|
||||
port = "3001"
|
||||
URL = "http://localhost:" + port
|
||||
page = requests.get(URL) # Use python requests to grab the rss-bridge main page
|
||||
soup = BeautifulSoup(page.content, "html.parser") # use bs4 to turn the page into soup
|
||||
bridges = soup.find_all("section") # get a soup-formatted list of all bridges on the rss-bridge page
|
||||
testBridges(bridges,status) # run the main scraping code with the list of bridges and the info if this is for the current version or the pr version
|
27
.github/workflows/documentation.yml
vendored
Normal file
27
.github/workflows/documentation.yml
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
name: Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'docs/**'
|
||||
|
||||
jobs:
|
||||
documentation:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@2.17.1
|
||||
with:
|
||||
php-version: 8.0
|
||||
- name: Install dependencies
|
||||
run: composer global require daux/daux.io
|
||||
- name: Generate documentation
|
||||
run: daux generate
|
||||
- name: Deploy same repository 🚀
|
||||
uses: JamesIves/github-pages-deploy-action@v4.2.5
|
||||
with:
|
||||
folder: "static"
|
||||
branch: gh-pages
|
13
.github/workflows/lint.yml
vendored
13
.github/workflows/lint.yml
vendored
@@ -33,3 +33,16 @@ jobs:
|
||||
- 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
|
||||
|
||||
executable_php_files_check:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- run: |
|
||||
if find -name "*.php" -executable -type f -print -exec false {} +
|
||||
then
|
||||
echo 'Good, no executable php scripts found'
|
||||
else
|
||||
echo 'Please unmark php scripts above as non-executable'
|
||||
exit 1
|
||||
fi
|
||||
|
69
.github/workflows/prhtmlgenerator.yml
vendored
Normal file
69
.github/workflows/prhtmlgenerator.yml
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
name: 'PR Testing'
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
test-pr:
|
||||
name: Generate HTML
|
||||
runs-on: ubuntu-latest
|
||||
# Needs additional permissions https://github.com/actions/first-interaction/issues/10#issuecomment-1041402989
|
||||
steps:
|
||||
- name: Check out self
|
||||
uses: actions/checkout@v2.3.2
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
- name: Check out rss-bridge
|
||||
run: |
|
||||
PR=${{github.event.number}};
|
||||
wget -O requirements.txt https://raw.githubusercontent.com/RSS-Bridge/rss-bridge/master/.github/prtester-requirements.txt;
|
||||
wget https://raw.githubusercontent.com/RSS-Bridge/rss-bridge/master/.github/prtester.py;
|
||||
wget https://patch-diff.githubusercontent.com/raw/$GITHUB_REPOSITORY/pull/$PR.patch;
|
||||
touch DEBUG;
|
||||
cat $PR.patch | grep " bridges/.*\.php" | sed "s= bridges/\(.*\)Bridge.php.*=\1=g" | sort | uniq > whitelist.txt
|
||||
- name: Start Docker - Current
|
||||
run: |
|
||||
docker run -d -v $GITHUB_WORKSPACE/whitelist.txt:/app/whitelist.txt -v $GITHUB_WORKSPACE/DEBUG:/app/DEBUG -p 3000:80 ghcr.io/rss-bridge/rss-bridge:latest
|
||||
- name: Start Docker - PR
|
||||
run: |
|
||||
docker build -t prbuild .;
|
||||
docker run -d -v $GITHUB_WORKSPACE/whitelist.txt:/app/whitelist.txt -v $GITHUB_WORKSPACE/DEBUG:/app/DEBUG -p 3001:80 prbuild
|
||||
- name: Setup python
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
cache: 'pip'
|
||||
- name: Install requirements
|
||||
run: |
|
||||
cd $GITHUB_WORKSPACE
|
||||
pip install -r requirements.txt
|
||||
- name: Run bridge tests
|
||||
id: testrun
|
||||
run: |
|
||||
mkdir results;
|
||||
python prtester.py;
|
||||
body="$(cat comment.txt)";
|
||||
body="${body//'%'/'%25'}";
|
||||
body="${body//$'\n'/'%0A'}";
|
||||
body="${body//$'\r'/'%0D'}";
|
||||
echo "::set-output name=bodylength::${#body}"
|
||||
echo "::set-output name=body::$body"
|
||||
- name: Find Comment
|
||||
if: ${{ steps.testrun.outputs.bodylength > 130 }}
|
||||
uses: peter-evans/find-comment@v2
|
||||
id: fc
|
||||
with:
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
comment-author: 'github-actions[bot]'
|
||||
body-includes: Pull request artifacts
|
||||
- name: Create or update comment
|
||||
if: ${{ steps.testrun.outputs.bodylength > 130 }}
|
||||
uses: peter-evans/create-or-update-comment@v2
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body: |
|
||||
${{ steps.testrun.outputs.body }}
|
||||
edit-mode: replace
|
25
.github/workflows/tests.yml
vendored
25
.github/workflows/tests.yml
vendored
@@ -7,19 +7,18 @@ on:
|
||||
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/
|
||||
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
|
||||
|
2
.gitignore
vendored
2
.gitignore
vendored
@@ -213,6 +213,7 @@ pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.phpunit.result.cache
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
@@ -228,6 +229,7 @@ pip-log.txt
|
||||
/whitelist.txt
|
||||
DEBUG
|
||||
config.ini.php
|
||||
config/*
|
||||
|
||||
######################
|
||||
## VisualStudioCode ##
|
||||
|
30
Dockerfile
30
Dockerfile
@@ -1,24 +1,24 @@
|
||||
FROM php:7-apache
|
||||
FROM php:7.4.29-fpm
|
||||
|
||||
LABEL description="RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one."
|
||||
LABEL repository="https://github.com/RSS-Bridge/rss-bridge"
|
||||
LABEL website="https://github.com/RSS-Bridge/rss-bridge"
|
||||
|
||||
ENV APACHE_DOCUMENT_ROOT=/app
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends \
|
||||
nginx \
|
||||
zlib1g-dev \
|
||||
libzip-dev \
|
||||
libmemcached-dev && \
|
||||
docker-php-ext-install zip && \
|
||||
pecl install memcached && \
|
||||
docker-php-ext-enable memcached && \
|
||||
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
|
||||
|
||||
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 ./config/nginx.conf /etc/nginx/sites-enabled/default
|
||||
|
||||
COPY --chown=www-data:www-data ./ /app/
|
||||
|
||||
CMD ["/app/docker-entrypoint.sh"]
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||
|
86
README.md
86
README.md
@@ -15,17 +15,17 @@ 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/)
|
||||
* `Cryptome` : Returns the most recent documents from [Cryptome.org](https://cryptome.org/)
|
||||
* `DansTonChat`: Most recent quotes from [danstonchat.com](https://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/) (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
|
||||
* `FlickrExplore` : [Latest interesting images](https://www.flickr.com/explore) from Flickr
|
||||
* `GoogleSearch` : Most recent results from Google Search
|
||||
* `Identi.ca` : Identica user timeline (Should be compatible with other Pump.io instances)
|
||||
* `Instagram`: Most recent photos from an Instagram user (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/)
|
||||
* `Instagram`: Most recent photos from an Instagram user (It is recommended to [configure](https://rss-bridge.github.io/rss-bridge/Bridge_Specific/Instagram.html) this bridge to work)
|
||||
* `OpenClassrooms`: Lastest tutorials from [openclassrooms.com](https://openclassrooms.com/)
|
||||
* `Pinterest`: Most recent photos from user or search
|
||||
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://secouchermoinsbete.fr/)
|
||||
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](https://secouchermoinsbete.fr/)
|
||||
* `ThePirateBay` : Returns the newest indexed torrents from [The Pirate Bay](https://thepiratebay.se/) with keywords
|
||||
* `Twitter` : Return keyword/hashtag search or user timeline
|
||||
* `Wikipedia`: highlighted articles from [Wikipedia](https://wikipedia.org/) in English, German, French or Esperanto
|
||||
@@ -44,20 +44,18 @@ RSS-Bridge is capable of producing several output formats:
|
||||
* `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)!
|
||||
You can extend RSS-Bridge with your own format, using the [Format API](https://rss-bridge.github.io/rss-bridge/Format_API/index.html)!
|
||||
|
||||
Screenshot
|
||||
===
|
||||
|
||||
Welcome screen:
|
||||
|
||||

|
||||
|
||||
***
|
||||

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

|
||||

|
||||
|
||||
Requirements
|
||||
===
|
||||
@@ -71,26 +69,29 @@ RSS-Bridge requires PHP 7.1 or higher with following extensions enabled:
|
||||
- [`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)
|
||||
- [`zip`](https://secure.php.net/manual/en/book.zip.php) (for some bridges)
|
||||
- [`sqlite3`](https://www.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)
|
||||
Find more information on our [Documentation](https://rss-bridge.github.io/rss-bridge/index.html)
|
||||
|
||||
Enable / Disable bridges
|
||||
===
|
||||
|
||||
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!
|
||||
|
||||
Find more information on the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting)
|
||||
Find more information on the [Documentation](https://rss-bridge.github.io/rss-bridge/For_Hosts/Whitelisting.html)
|
||||
|
||||
**Notice**: By default, RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
|
||||
**Notice**: By default, RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://rss-bridge.github.io/rss-bridge/For_Hosts/Whitelisting.html) 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!
|
||||
*Note: External providers' applications are packaged by 3rd parties. Use at your own discretion.*
|
||||
|
||||
[](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
|
||||
[](https://heroku.com/deploy)
|
||||
[](https://www.cloudron.io/store/com.rssbridgeapp.cloudronapp.html)
|
||||
|
||||
Getting involved
|
||||
===
|
||||
@@ -102,13 +103,13 @@ There are many ways for you to getting involved with RSS-Bridge. Here are a few
|
||||
- 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)
|
||||
- Improve the [Documentation](https://rss-bridge.github.io/rss-bridge/)
|
||||
- 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).
|
||||
We are RSS-Bridge community, a group of developers continuing the project initiated by sebsauvage, webmaster of [sebsauvage.net](https://sebsauvage.net), author of [Shaarli](https://sebsauvage.net/wiki/doku.php?id=php:shaarli) and [ZeroBin](https://sebsauvage.net/wiki/doku.php?id=php:zerobin).
|
||||
|
||||
**Contributors** (sorted alphabetically):
|
||||
<!--
|
||||
@@ -124,18 +125,20 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [aledeg](https://github.com/aledeg)
|
||||
* [alex73](https://github.com/alex73)
|
||||
* [alexAubin](https://github.com/alexAubin)
|
||||
* [Alkarex](https://github.com/Alkarex)
|
||||
* [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)
|
||||
* [austinhuang0131](https://github.com/austinhuang0131)
|
||||
* [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)
|
||||
* [BoboTiG](https://github.com/BoboTiG)
|
||||
* [Bockiii](https://github.com/Bockiii)
|
||||
* [captn3m0](https://github.com/captn3m0)
|
||||
* [chemel](https://github.com/chemel)
|
||||
@@ -161,7 +164,9 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
|
||||
* [drego85](https://github.com/drego85)
|
||||
* [drklee3](https://github.com/drklee3)
|
||||
* [DRogueRonin](https://github.com/DRogueRonin)
|
||||
* [dvikan](https://github.com/dvikan)
|
||||
* [eggwhalefrog](https://github.com/eggwhalefrog)
|
||||
* [em92](https://github.com/em92)
|
||||
* [eMerzh](https://github.com/eMerzh)
|
||||
* [EtienneM](https://github.com/EtienneM)
|
||||
@@ -175,6 +180,7 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [Frenzie](https://github.com/Frenzie)
|
||||
* [fulmeek](https://github.com/fulmeek)
|
||||
* [ggiessen](https://github.com/ggiessen)
|
||||
* [gileri](https://github.com/gileri)
|
||||
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
|
||||
* [girlpunk](https://github.com/girlpunk)
|
||||
* [Glandos](https://github.com/Glandos)
|
||||
@@ -189,9 +195,11 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [hunhejj](https://github.com/hunhejj)
|
||||
* [husim0](https://github.com/husim0)
|
||||
* [IceWreck](https://github.com/IceWreck)
|
||||
* [imagoiq](https://github.com/imagoiq)
|
||||
* [j0k3r](https://github.com/j0k3r)
|
||||
* [JackNUMBER](https://github.com/JackNUMBER)
|
||||
* [jacquesh](https://github.com/jacquesh)
|
||||
* [jakubvalenta](https://github.com/jakubvalenta)
|
||||
* [JasonGhent](https://github.com/JasonGhent)
|
||||
* [jcgoette](https://github.com/jcgoette)
|
||||
* [jdesgats](https://github.com/jdesgats)
|
||||
@@ -203,15 +211,21 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [johnnygroovy](https://github.com/johnnygroovy)
|
||||
* [johnpc](https://github.com/johnpc)
|
||||
* [joni1993](https://github.com/joni1993)
|
||||
* [jtojnar](https://github.com/jtojnar)
|
||||
* [KamaleiZestri](https://github.com/KamaleiZestri)
|
||||
* [kkoyung](https://github.com/kkoyung)
|
||||
* [klimplant](https://github.com/klimplant)
|
||||
* [KN4CK3R](https://github.com/KN4CK3R)
|
||||
* [kolarcz](https://github.com/kolarcz)
|
||||
* [kranack](https://github.com/kranack)
|
||||
* [kraoc](https://github.com/kraoc)
|
||||
* [krisu5](https://github.com/krisu5)
|
||||
* [l1n](https://github.com/l1n)
|
||||
* [laBecasse](https://github.com/laBecasse)
|
||||
* [lagaisse](https://github.com/lagaisse)
|
||||
* [lalannev](https://github.com/lalannev)
|
||||
* [langfingaz](https://github.com/langfingaz)
|
||||
* [lassana](https://github.com/lassana)
|
||||
* [ldidry](https://github.com/ldidry)
|
||||
* [Leomaradan](https://github.com/Leomaradan)
|
||||
* [leyrer](https://github.com/leyrer)
|
||||
@@ -223,13 +237,14 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [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)
|
||||
* [marius8510000-bot](https://github.com/marius8510000-bot)
|
||||
* [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)
|
||||
* [mickaelBert](https://github.com/mickaelBert)
|
||||
* [mightymt](https://github.com/mightymt)
|
||||
* [mitsukarenai](https://github.com/mitsukarenai)
|
||||
* [Monocularity](https://github.com/Monocularity)
|
||||
@@ -237,10 +252,14 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [mr-flibble](https://github.com/mr-flibble)
|
||||
* [mro](https://github.com/mro)
|
||||
* [mschwld](https://github.com/mschwld)
|
||||
* [muekoeff](https://github.com/muekoeff)
|
||||
* [mw80](https://github.com/mw80)
|
||||
* [mxmehl](https://github.com/mxmehl)
|
||||
* [Mynacol](https://github.com/Mynacol)
|
||||
* [nel50n](https://github.com/nel50n)
|
||||
* [niawag](https://github.com/niawag)
|
||||
* [Niehztog](https://github.com/Niehztog)
|
||||
* [NikNikYkt](https://github.com/NikNikYkt)
|
||||
* [Nono-m0le](https://github.com/Nono-m0le)
|
||||
* [obsiwitch](https://github.com/obsiwitch)
|
||||
* [Ololbu](https://github.com/Ololbu)
|
||||
@@ -248,18 +267,23 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [otakuf](https://github.com/otakuf)
|
||||
* [Park0](https://github.com/Park0)
|
||||
* [Paroleen](https://github.com/Paroleen)
|
||||
* [Patricol](https://github.com/Patricol)
|
||||
* [paulchen](https://github.com/paulchen)
|
||||
* [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)
|
||||
* [pirnz](https://github.com/pirnz)
|
||||
* [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)
|
||||
* [pubak42](https://github.com/pubak42)
|
||||
* [Qluxzz](https://github.com/Qluxzz)
|
||||
* [quentinus95](https://github.com/quentinus95)
|
||||
* [quickwick](https://github.com/quickwick)
|
||||
* [rakoo](https://github.com/rakoo)
|
||||
* [RawkBob](https://github.com/RawkBob)
|
||||
* [regisenguehard](https://github.com/regisenguehard)
|
||||
@@ -268,6 +292,7 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [Roliga](https://github.com/Roliga)
|
||||
* [ronansalmon](https://github.com/ronansalmon)
|
||||
* [rremizov](https://github.com/rremizov)
|
||||
* [s0lesurviv0r](https://github.com/s0lesurviv0r)
|
||||
* [sal0max](https://github.com/sal0max)
|
||||
* [sebsauvage](https://github.com/sebsauvage)
|
||||
* [shutosg](https://github.com/shutosg)
|
||||
@@ -275,7 +300,9 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [Simounet](https://github.com/Simounet)
|
||||
* [somini](https://github.com/somini)
|
||||
* [SpangleLabs](https://github.com/SpangleLabs)
|
||||
* [SqrtMinusOne](https://github.com/SqrtMinusOne)
|
||||
* [squeek502](https://github.com/squeek502)
|
||||
* [StelFux](https://github.com/StelFux)
|
||||
* [stjohnjohnson](https://github.com/stjohnjohnson)
|
||||
* [Stopka](https://github.com/Stopka)
|
||||
* [Strubbl](https://github.com/Strubbl)
|
||||
@@ -288,14 +315,21 @@ Use this script to generate the list automatically (using the GitHub API):
|
||||
* [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)
|
||||
* [thibaultcouraud](https://github.com/thibaultcouraud)
|
||||
* [timendum](https://github.com/timendum)
|
||||
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
|
||||
* [tomaszkane](https://github.com/tomaszkane)
|
||||
* [tomershvueli](https://github.com/tomershvueli)
|
||||
* [TotalCaesar659](https://github.com/TotalCaesar659)
|
||||
* [tpikonen](https://github.com/tpikonen)
|
||||
* [TReKiE](https://github.com/TReKiE)
|
||||
* [triatic](https://github.com/triatic)
|
||||
* [User123698745](https://github.com/User123698745)
|
||||
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
|
||||
* [vitkabele](https://github.com/vitkabele)
|
||||
* [WalterBarrett](https://github.com/WalterBarrett)
|
||||
* [wtuuju](https://github.com/wtuuju)
|
||||
* [xurxof](https://github.com/xurxof)
|
||||
@@ -312,16 +346,18 @@ 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)
|
||||
* [`Parsedown`](https://github.com/erusev/parsedown) licensed under the [MIT License](https://opensource.org/licenses/MIT)
|
||||
* [`PHP Simple HTML DOM Parser`](https://simplehtmldom.sourceforge.io/docs/1.9/index.html) licensed under the [MIT License](https://opensource.org/licenses/MIT)
|
||||
* [`php-urljoin`](https://github.com/fluffy-critter/php-urljoin) licensed under the [MIT License](https://opensource.org/licenses/MIT)
|
||||
* [php polyfills](https://github.com/symfony/polyfill) licensed under the [MIT License](https://opensource.org/licenses/MIT)
|
||||
* [`Laravel framework`](https://github.com/laravel/framework/) licensed under the [MIT License](https://opensource.org/licenses/MIT)
|
||||
|
||||
Technical notes
|
||||
===
|
||||
|
||||
* 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)
|
||||
* You can implement your own bridge, [following these instructions](https://rss-bridge.github.io/rss-bridge/Bridge_API/index.html).
|
||||
* You can enable debug mode to disable caching. Find more information on the [Wiki](https://rss-bridge.github.io/rss-bridge/For_Developers/Debug_mode.html)
|
||||
|
||||
Rant
|
||||
===
|
||||
|
@@ -25,7 +25,7 @@ class ConnectivityAction extends ActionAbstract {
|
||||
public function execute() {
|
||||
|
||||
if(!Debug::isEnabled()) {
|
||||
returnError('This action is only available in debug mode!');
|
||||
returnError('This action is only available in debug mode!', 400);
|
||||
}
|
||||
|
||||
if(!isset($this->userData['bridge'])) {
|
||||
@@ -55,7 +55,6 @@ class ConnectivityAction extends ActionAbstract {
|
||||
private function reportBridgeConnectivity($bridgeName) {
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
if(!$bridgeFac->isWhitelisted($bridgeName)) {
|
||||
header('Content-Type: text/html');
|
||||
@@ -84,12 +83,10 @@ class ConnectivityAction extends ActionAbstract {
|
||||
try {
|
||||
$reply = getContents($bridge::URI, array(), $curl_opts, true);
|
||||
|
||||
if($reply) {
|
||||
if($reply['code'] === 200) {
|
||||
$retVal['successful'] = true;
|
||||
if (isset($reply['header'])) {
|
||||
if (strpos($reply['header'], 'HTTP/1.1 301 Moved Permanently') !== false) {
|
||||
$retVal['http_code'] = 301;
|
||||
}
|
||||
if (strpos(implode('', $reply['status_lines']), '301 Moved Permanently')) {
|
||||
$retVal['http_code'] = 301;
|
||||
}
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
|
@@ -20,7 +20,6 @@ class DetectAction extends ActionAbstract {
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
|
||||
|
||||
|
@@ -28,7 +28,6 @@ class DisplayAction extends ActionAbstract {
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
// whitelist control
|
||||
if(!$bridgeFac->isWhitelisted($bridge)) {
|
||||
@@ -38,6 +37,7 @@ class DisplayAction extends ActionAbstract {
|
||||
|
||||
// Data retrieval
|
||||
$bridge = $bridgeFac->create($bridge);
|
||||
$bridge->loadConfiguration();
|
||||
|
||||
$noproxy = array_key_exists('_noproxy', $this->userData)
|
||||
&& filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
||||
@@ -131,7 +131,6 @@ class DisplayAction extends ActionAbstract {
|
||||
|
||||
try {
|
||||
$bridge->setDatas($bridge_params);
|
||||
$bridge->loadConfiguration();
|
||||
$bridge->collectData();
|
||||
|
||||
$items = $bridge->getItems();
|
||||
@@ -245,8 +244,14 @@ class DisplayAction extends ActionAbstract {
|
||||
$format = $formatFac->create($format);
|
||||
$format->setItems($items);
|
||||
$format->setExtraInfos($infos);
|
||||
$format->setLastModified($cache->getTime());
|
||||
$format->display();
|
||||
$lastModified = $cache->getTime();
|
||||
$format->setLastModified($lastModified);
|
||||
if ($lastModified) {
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $lastModified) . 'GMT');
|
||||
}
|
||||
header('Content-Type: ' . $format->getMimeType() . '; charset=' . $format->getCharset());
|
||||
|
||||
echo $format->stringify();
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
header('Content-Type: text/html', true, $e->getCode());
|
||||
|
@@ -18,7 +18,6 @@ class ListAction extends ActionAbstract {
|
||||
$list->total = 0;
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
|
||||
|
||||
|
4
app.json
4
app.json
@@ -1,8 +1,8 @@
|
||||
{
|
||||
"service": "Heroku",
|
||||
"name": "RSS-Bridge",
|
||||
"name": "rss-bridge-heroku",
|
||||
"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",
|
||||
"repository": "https://github.com/RSS-Bridge/rss-bridge?1651005770",
|
||||
"keywords": ["php", "rss-bridge", "rss"]
|
||||
}
|
||||
|
||||
|
@@ -27,7 +27,7 @@ class ABCNewsBridge extends BridgeAbstract {
|
||||
|
||||
public function collectData() {
|
||||
$url = 'https://www.abc.net.au/news/' . $this->getInput('topic');
|
||||
$html = getSimpleHTMLDOM($url)->find('.YAJzu._26IxR._2kxNB._3BZxh', 0);
|
||||
$html = getSimpleHTMLDOM($url)->find('.YAJzu._2FvRw.ZWhbj._3BZxh', 0);
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
foreach($html->find('._2H7Su') as $article) {
|
||||
|
@@ -1,42 +0,0 @@
|
||||
<?php
|
||||
class ABCTabsBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'kranack';
|
||||
const NAME = 'ABC Tabs Bridge';
|
||||
const URI = 'https://www.abc-tabs.com/';
|
||||
const DESCRIPTION = 'Returns 22 newest tabs';
|
||||
|
||||
public function collectData(){
|
||||
$html = '';
|
||||
$html = getSimpleHTMLDOM(static::URI . 'tablatures/nouveautes.html')
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
$table = $html->find('table#myTable', 0)->children(1);
|
||||
|
||||
foreach ($table->find('tr') as $tab) {
|
||||
$item = array();
|
||||
$item['author'] = $tab->find('td', 1)->plaintext
|
||||
. ' - '
|
||||
. $tab->find('td', 2)->plaintext;
|
||||
|
||||
$item['title'] = $tab->find('td', 1)->plaintext
|
||||
. ' - '
|
||||
. $tab->find('td', 2)->plaintext;
|
||||
|
||||
$item['content'] = 'Le '
|
||||
. $tab->find('td', 0)->plaintext
|
||||
. '<br> Par: '
|
||||
. $tab->find('td', 5)->plaintext
|
||||
. '<br> Type: '
|
||||
. $tab->find('td', 3)->plaintext;
|
||||
|
||||
$item['id'] = static::URI
|
||||
. $tab->find('td', 2)->find('a', 0)->getAttribute('href');
|
||||
|
||||
$item['uri'] = static::URI
|
||||
. $tab->find('td', 2)->find('a', 0)->getAttribute('href');
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,8 +12,7 @@ class AO3Bridge extends BridgeAbstract {
|
||||
'name' => 'url',
|
||||
'required' => true,
|
||||
// Example: F/F tag, complete works only
|
||||
'exampleValue' => self::URI
|
||||
. 'works?work_search[complete]=T&tag_id=F*s*F',
|
||||
'exampleValue' => 'https://archiveofourown.org/works?work_search[complete]=T&tag_id=F*s*F',
|
||||
),
|
||||
),
|
||||
'Bookmarks' => array(
|
||||
|
@@ -51,6 +51,8 @@ class ARDMediathekBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$oldTz = date_default_timezone_get();
|
||||
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
|
||||
$pathComponents = explode('/', $this->getInput('path'));
|
||||
@@ -87,5 +89,7 @@ class ARDMediathekBridge extends BridgeAbstract {
|
||||
$item['author'] = $video->publicationService->name;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
date_default_timezone_set($oldTz);
|
||||
}
|
||||
}
|
||||
|
@@ -3,12 +3,25 @@ class AcrimedBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'qwertygc';
|
||||
const NAME = 'Acrimed Bridge';
|
||||
const URI = 'http://www.acrimed.org/';
|
||||
const URI = 'https://www.acrimed.org/';
|
||||
const CACHE_TIMEOUT = 4800; //2hours
|
||||
const DESCRIPTION = 'Returns the newest articles';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'limit' => [
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'defaultValue' => -1,
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas(static::URI . 'spip.php?page=backend');
|
||||
$this->collectExpandableDatas(
|
||||
static::URI . 'spip.php?page=backend',
|
||||
$this->getInput('limit')
|
||||
);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
|
@@ -11,6 +11,7 @@ class AlbionOnlineBridge extends BridgeAbstract {
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 5,
|
||||
),
|
||||
|
83
bridges/AlfaBankByBridge.php
Normal file
83
bridges/AlfaBankByBridge.php
Normal file
@@ -0,0 +1,83 @@
|
||||
<?php
|
||||
class AlfaBankByBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'lassana';
|
||||
const NAME = 'AlfaBank.by Новости';
|
||||
const URI = 'https://www.alfabank.by';
|
||||
const DESCRIPTION = 'Уведомления Alfa-Now — новости от Альфа-Банка';
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
const PARAMETERS = array(
|
||||
'News' => array(
|
||||
'business' => array(
|
||||
'name' => 'Альфа Бизнес',
|
||||
'type' => 'list',
|
||||
'title' => 'В зависимости от выбора, возращает уведомления для" .
|
||||
" клиентов физ. лиц либо для клиентов-юридических лиц и ИП',
|
||||
'values' => array(
|
||||
'Новости' => 'news',
|
||||
'Новости бизнеса' => 'newsBusiness'
|
||||
),
|
||||
'defaultValue' => 'news'
|
||||
),
|
||||
'fullContent' => array(
|
||||
'name' => 'Включать содержимое',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Если выбрано, содержимое уведомлений вставляется в поток (работает медленно)'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$business = $this->getInput('business') == 'newsBusiness';
|
||||
$fullContent = $this->getInput('fullContent') == 'on';
|
||||
|
||||
$mainPageUrl = self::URI . '/about/articles/uvedomleniya/';
|
||||
if($business) {
|
||||
$mainPageUrl .= '?business=true';
|
||||
}
|
||||
$html = getSimpleHTMLDOM($mainPageUrl);
|
||||
$limit = 0;
|
||||
|
||||
foreach($html->find('a.notifications__item') as $element) {
|
||||
if($limit < 10) {
|
||||
$item = array();
|
||||
$item['uid'] = 'urn:sha1:' . hash('sha1', $element->getAttribute('data-notification-id'));
|
||||
$item['title'] = $element->find('div.item-title', 0)->innertext;
|
||||
$item['timestamp'] = DateTime::createFromFormat(
|
||||
'd M Y',
|
||||
$this->ruMonthsToEn($element->find('div.item-date', 0)->innertext)
|
||||
)->getTimestamp();
|
||||
|
||||
$itemUrl = self::URI . $element->href;
|
||||
if($business) {
|
||||
$itemUrl = str_replace('?business=true', '', $itemUrl);
|
||||
}
|
||||
$item['uri'] = $itemUrl;
|
||||
|
||||
if($fullContent) {
|
||||
$itemHtml = getSimpleHTMLDOM($itemUrl);
|
||||
if($itemHtml) {
|
||||
$item['content'] = $itemHtml->find('div.now-p__content-text', 0)->innertext;
|
||||
}
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
$limit++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return static::URI . '/local/images/favicon.ico';
|
||||
}
|
||||
|
||||
private function ruMonthsToEn($date) {
|
||||
$ruMonths = array(
|
||||
'Января', 'Февраля', 'Марта', 'Апреля', 'Мая', 'Июня',
|
||||
'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря' );
|
||||
$enMonths = array(
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December' );
|
||||
return str_replace($ruMonths, $enMonths, $date);
|
||||
}
|
||||
}
|
@@ -4,7 +4,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'superbaillot.net';
|
||||
const NAME = 'Allo Cine Bridge';
|
||||
const CACHE_TIMEOUT = 25200; // 7h
|
||||
const URI = 'http://www.allocine.fr/';
|
||||
const URI = 'https://www.allocine.fr';
|
||||
const DESCRIPTION = 'Bridge for allocine.fr';
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
@@ -35,26 +35,26 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
|
||||
$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/'
|
||||
'faux-raccord' => '/video/programme-12284/',
|
||||
'fanzone' => '/video/programme-12298/',
|
||||
'game-in-cine' => '/video/programme-12288/',
|
||||
'pour-la-faire-courte' => '/video/programme-20960/',
|
||||
'home-cinema' => '/video/programme-12287/',
|
||||
'pils-par-ici-les-sorties' => '/video/programme-25789/',
|
||||
'allocine-lemission-sur-lestream' => '/video/programme-25123/',
|
||||
'give-me-five' => '/video/programme-21919/saison-34518/',
|
||||
'aviez-vous-remarque' => '/video/programme-19518/',
|
||||
'et-paf-il-est-mort' => '/video/programme-25113/',
|
||||
'the-big-fan-theory' => '/video/programme-20403/',
|
||||
'cliches' => '/video/programme-24834/',
|
||||
'completement' => '/video/programme-23859/',
|
||||
'fun-facts' => '/video/programme-23040/',
|
||||
'origin-story' => '/video/programme-25667/'
|
||||
);
|
||||
|
||||
$category = $this->getInput('category');
|
||||
if(array_key_exists($category, $categories)) {
|
||||
return static::URI . $categories[$category];
|
||||
return static::URI . $this->getLastSeasonURI($categories[$category]);
|
||||
} else {
|
||||
returnClientError('Emission inconnue');
|
||||
}
|
||||
@@ -63,6 +63,14 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
private function getLastSeasonURI($category)
|
||||
{
|
||||
$html = getSimpleHTMLDOMCached(static::URI . $category, 86400);
|
||||
$seasonLink = $html->find('section[class=section-wrap section]', 0)->find('div[class=cf]', 0)->find('a', 0);
|
||||
$URI = $seasonLink->href;
|
||||
return $URI;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
return self::NAME . ' : '
|
||||
@@ -83,12 +91,11 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
||||
foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) {
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('a[class*=meta-title-link]', 0);
|
||||
$content = trim($element->outertext);
|
||||
$content = trim(defaultLinkTo($element->outertext, static::URI));
|
||||
|
||||
// Replace image 'src' with the one in 'data-src'
|
||||
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content);
|
||||
@@ -99,7 +106,7 @@ class AllocineFRBridge extends BridgeAbstract {
|
||||
|
||||
$item['content'] = $content;
|
||||
$item['title'] = trim($title->innertext);
|
||||
$item['uri'] = static::URI . substr($title->href, 1);
|
||||
$item['uri'] = static::URI . '/' . substr($title->href, 1);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ class AmazonBridge extends BridgeAbstract {
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'required' => true,
|
||||
'exampleValue' => 'watch',
|
||||
),
|
||||
'sort' => array(
|
||||
'name' => 'Sort by',
|
||||
@@ -41,6 +42,8 @@ class AmazonBridge extends BridgeAbstract {
|
||||
'Mexico' => 'com.mx',
|
||||
'Netherlands' => 'nl',
|
||||
'Spain' => 'es',
|
||||
'Sweden' => 'se',
|
||||
'Turkey' => 'com.tr',
|
||||
'United Kingdom' => 'co.uk',
|
||||
'United States' => 'com',
|
||||
),
|
||||
@@ -48,6 +51,48 @@ class AmazonBridge extends BridgeAbstract {
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$baseUrl = sprintf('https://www.amazon.%s', $this->getInput('tld'));
|
||||
|
||||
$url = sprintf(
|
||||
'%s/s/?field-keywords=%s&sort=%s',
|
||||
$baseUrl,
|
||||
urlencode($this->getInput('q')),
|
||||
$this->getInput('sort')
|
||||
);
|
||||
|
||||
$dom = getSimpleHTMLDOM($url);
|
||||
|
||||
$elements = $dom->find('div.s-result-item');
|
||||
|
||||
foreach($elements as $element) {
|
||||
$item = [];
|
||||
|
||||
$title = $element->find('h2', 0);
|
||||
if (!$title) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = $title->innertext;
|
||||
|
||||
$itemUrl = $element->find('a', 0)->href;
|
||||
$item['uri'] = urljoin($baseUrl, $itemUrl);
|
||||
|
||||
$image = $element->find('img', 0);
|
||||
if ($image) {
|
||||
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />';
|
||||
}
|
||||
|
||||
$price = $element->find('span.a-price > .a-offscreen', 0);
|
||||
if ($price) {
|
||||
$item['content'] .= $price->innertext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
|
||||
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
|
||||
@@ -55,40 +100,4 @@ class AmazonBridge extends BridgeAbstract {
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
|
||||
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
foreach($html->find('li.s-result-item') as $element) {
|
||||
|
||||
$item = array();
|
||||
|
||||
// Title
|
||||
$title = $element->find('h2', 0);
|
||||
if (is_null($title)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);
|
||||
|
||||
// Url
|
||||
$uri = $title->parent()->getAttribute('href');
|
||||
$uri = substr($uri, 0, strrpos($uri, '/'));
|
||||
|
||||
$item['uri'] = substr($uri, 0, strrpos($uri, '/'));
|
||||
|
||||
// Content
|
||||
$image = $element->find('img', 0);
|
||||
$price = $element->find('span.s-price', 0);
|
||||
$price = ($price) ? $price->innertext : '';
|
||||
|
||||
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />' . $price;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -20,7 +20,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
'name' => 'Country',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Australia' => 'com.au',
|
||||
'Australia' => 'com.au',
|
||||
'Brazil' => 'com.br',
|
||||
'Canada' => 'ca',
|
||||
'China' => 'cn',
|
||||
@@ -30,9 +30,10 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
'Italy' => 'it',
|
||||
'Japan' => 'co.jp',
|
||||
'Mexico' => 'com.mx',
|
||||
'Netherlands' => 'nl',
|
||||
'Netherlands' => 'nl',
|
||||
'Spain' => 'es',
|
||||
'Sweden' => 'se',
|
||||
'Turkey' => 'com.tr',
|
||||
'United Kingdom' => 'co.uk',
|
||||
'United States' => 'com',
|
||||
),
|
||||
@@ -49,6 +50,8 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
|
||||
'.a-color-price',
|
||||
);
|
||||
|
||||
const WHITESPACE = " \t\n\r\0\x0B\xC2\xA0";
|
||||
|
||||
protected $title;
|
||||
|
||||
/**
|
||||
@@ -154,6 +157,22 @@ EOT;
|
||||
return false;
|
||||
}
|
||||
|
||||
private function scrapePriceTwister($html) {
|
||||
$str = $html->find('.twister-plus-buying-options-price-data', 0);
|
||||
|
||||
$data = json_decode($str->innertext, true);
|
||||
if(count($data) === 1) {
|
||||
$data = $data[0];
|
||||
return array(
|
||||
'displayPrice' => $data['displayPrice'],
|
||||
'currency' => $data['currency'],
|
||||
'shipping' => '0',
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function scrapePriceGeneric($html) {
|
||||
$priceDiv = null;
|
||||
|
||||
@@ -168,12 +187,11 @@ EOT;
|
||||
return false;
|
||||
}
|
||||
|
||||
$priceString = $priceDiv->plaintext;
|
||||
|
||||
preg_match('/[\d.,]+/', $priceString, $matches);
|
||||
$priceString = str_replace(str_split(self::WHITESPACE), '', $priceDiv->plaintext);
|
||||
preg_match('/(\d+\.\d{0,2})/', $priceString, $matches);
|
||||
|
||||
$price = $matches[0];
|
||||
$currency = trim(str_replace($price, '', $priceString), " \t\n\r\0\x0B\xC2\xA0");
|
||||
$currency = str_replace($price, '', $priceString);
|
||||
|
||||
if ($price != null && $currency != null) {
|
||||
return array(
|
||||
@@ -186,6 +204,21 @@ EOT;
|
||||
return false;
|
||||
}
|
||||
|
||||
private function renderContent($image, $data) {
|
||||
$price = $data['displayPrice'];
|
||||
if (!$price) {
|
||||
$price = "{$data['price']} {$data['currency']}";
|
||||
}
|
||||
|
||||
$html = "$image<br>Price: $price";
|
||||
|
||||
if ($data['shipping'] !== '0') {
|
||||
$html .= "<br>Shipping: {$data['shipping']} {$data['currency']}</br>";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape method for Amazon product page
|
||||
* @return [type] [description]
|
||||
@@ -195,20 +228,16 @@ EOT;
|
||||
$this->title = $this->getTitle($html);
|
||||
$imageTag = $this->getImage($html);
|
||||
|
||||
$data = $this->scrapePriceFromMetrics($html) ?: $this->scrapePriceGeneric($html);
|
||||
$data = $this->scrapePriceGeneric($html);
|
||||
|
||||
$item = array(
|
||||
'title' => $this->title,
|
||||
'uri' => $this->getURI(),
|
||||
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
|
||||
'content' => $this->renderContent($imageTag, $data),
|
||||
// This is to ensure that feed readers notice the price change
|
||||
'uid' => md5($data['price'])
|
||||
);
|
||||
|
||||
if ($data['shipping'] !== '0') {
|
||||
$item['content'] .= "<br>Shipping: {$data['shipping']} {$data['currency']}</br>";
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -37,9 +37,11 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
$processedOK = 0;
|
||||
foreach (array($thismonth, $lastmonth) as $requestFilter) {
|
||||
|
||||
//Retrive page contents
|
||||
$url = self::URI . 'history-0-1/' . $requestFilter;
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$html = getContents($url);
|
||||
// Convert html from iso-8859-1 => utf8
|
||||
$html = utf8_encode($html);
|
||||
$html = str_get_html($html);
|
||||
|
||||
//Relases are sorted by day : process each day individually
|
||||
foreach($html->find('div.history', 0)->find('h3') as $daySection) {
|
||||
@@ -87,6 +89,8 @@ class AnimeUltimeBridge extends BridgeAbstract {
|
||||
|
||||
// Retrieve description from description page
|
||||
$html_item = getContents($item_uri);
|
||||
// Convert html from iso-8859-1 => utf8
|
||||
$html_item = utf8_encode($html_item);
|
||||
$item_description = substr(
|
||||
$html_item,
|
||||
strpos($html_item, 'class="principal_contain" align="center">') + 41
|
||||
|
@@ -94,6 +94,7 @@ class AppleAppStoreBridge extends BridgeAbstract {
|
||||
|
||||
$headers = array(
|
||||
"Authorization: Bearer $token",
|
||||
'Origin: https://apps.apple.com',
|
||||
);
|
||||
|
||||
$json = json_decode(getContents($uri, $headers), true);
|
||||
|
@@ -10,7 +10,8 @@ class ArtStationBridge extends BridgeAbstract {
|
||||
'Search Query' => array(
|
||||
'q' => array(
|
||||
'name' => 'Search term',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'bird'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@@ -1,89 +1,122 @@
|
||||
<?php
|
||||
class Arte7Bridge extends BridgeAbstract {
|
||||
|
||||
// const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Arte +7';
|
||||
const URI = 'https://www.arte.tv/';
|
||||
const MAINTAINER = 'imagoiq';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns newest videos from ARTE +7';
|
||||
|
||||
const API_TOKEN = 'Nzc1Yjc1ZjJkYjk1NWFhN2I2MWEwMmRlMzAzNjI5NmU3NWU3ODg4ODJjOWMxNTMxYzEzZGRjYjg2ZGE4MmIwOA';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Catégorie (Français)' => array(
|
||||
'catfr' => array(
|
||||
'global' => [
|
||||
'sort_by' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Catégorie',
|
||||
'name' => 'Sort by',
|
||||
'required' => false,
|
||||
'defaultValue' => null,
|
||||
'values' => array(
|
||||
'Toutes les vidéos (français)' => null,
|
||||
'Actu & société' => 'ACT',
|
||||
'Séries & fiction' => 'SER',
|
||||
'Cinéma' => 'CIN',
|
||||
'Arts & spectacles classiques' => 'ARS',
|
||||
'Default' => null,
|
||||
'Video rights start date' => 'videoRightsBegin',
|
||||
'Video rights end date' => 'videoRightsEnd',
|
||||
'Brodcast date' => 'broadcastBegin',
|
||||
'Creation date' => 'creationDate',
|
||||
'Last modified' => 'lastModified',
|
||||
'Number of views' => 'views',
|
||||
'Number of views per period' => 'viewsPeriod',
|
||||
'Available screens' => 'availableScreens',
|
||||
'Episode' => 'episode'
|
||||
),
|
||||
),
|
||||
'sort_direction' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Sort direction',
|
||||
'required' => false,
|
||||
'defaultValue' => 'DESC',
|
||||
'values' => array(
|
||||
'Ascending' => 'ASC',
|
||||
'Descending' => 'DESC'
|
||||
),
|
||||
),
|
||||
'exclude_trailers' => [
|
||||
'name' => 'Exclude trailers',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'defaultValue' => false
|
||||
],
|
||||
],
|
||||
'Category' => array(
|
||||
'lang' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Language',
|
||||
'values' => array(
|
||||
'Français' => 'fr',
|
||||
'Deutsch' => 'de',
|
||||
'English' => 'en',
|
||||
'Español' => 'es',
|
||||
'Polski' => 'pl',
|
||||
'Italiano' => 'it'
|
||||
),
|
||||
),
|
||||
'cat' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Category',
|
||||
'values' => array(
|
||||
'All videos' => null,
|
||||
'News & society' => 'ACT',
|
||||
'Series & fiction' => 'SER',
|
||||
'Cinema' => 'CIN',
|
||||
'Culture' => 'ARS',
|
||||
'Culture pop' => 'CPO',
|
||||
'Découverte' => 'DEC',
|
||||
'Histoire' => 'HIST',
|
||||
'Discovery' => 'DEC',
|
||||
'History' => 'HIST',
|
||||
'Science' => 'SCI',
|
||||
'Autre' => 'AUT'
|
||||
'Other' => 'AUT'
|
||||
)
|
||||
)
|
||||
),
|
||||
),
|
||||
'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(
|
||||
'Collection' => array(
|
||||
'lang' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Catégorie',
|
||||
'name' => 'Language',
|
||||
'values' => array(
|
||||
'Alle Videos (deutsch)' => null,
|
||||
'Aktuelles & Gesellschaft' => 'ACT',
|
||||
'Fernsehfilme & Serien' => 'SER',
|
||||
'Kino' => 'CIN',
|
||||
'Kunst & Kultur' => 'ARS',
|
||||
'Popkultur & Alternativ' => 'CPO',
|
||||
'Entdeckung' => 'DEC',
|
||||
'Geschichte' => 'HIST',
|
||||
'Wissenschaft' => 'SCI',
|
||||
'Sonstiges' => 'AUT'
|
||||
'Français' => 'fr',
|
||||
'Deutsch' => 'de',
|
||||
'English' => 'en',
|
||||
'Español' => 'es',
|
||||
'Polski' => 'pl',
|
||||
'Italiano' => 'it'
|
||||
)
|
||||
)
|
||||
),
|
||||
'Collection (Allemand)' => array(
|
||||
'colde' => array(
|
||||
),
|
||||
'col' => array(
|
||||
'name' => 'Collection id',
|
||||
'required' => true,
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/'
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/',
|
||||
'exampleValue' => 'RC-014095'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Catégorie (Français)':
|
||||
$category = $this->getInput('catfr');
|
||||
$lang = 'fr';
|
||||
case 'Category':
|
||||
$category = $this->getInput('cat');
|
||||
$collectionId = null;
|
||||
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');
|
||||
case 'Collection':
|
||||
$collectionId = $this->getInput('col');
|
||||
$category = null;
|
||||
break;
|
||||
}
|
||||
|
||||
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=10&language='
|
||||
$lang = $this->getInput('lang');
|
||||
$sort_by = $this->getInput('sort_by');
|
||||
$sort_direction = $this->getInput('sort_direction') == 'ASC' ? '' : '-';
|
||||
|
||||
$url = 'https://api.arte.tv/api/opa/v3/videos?limit=15&language='
|
||||
. $lang
|
||||
. ($sort_by != null ? '&sort=' . $sort_direction . $sort_by : '')
|
||||
. ($category != null ? '&category.code=' . $category : '')
|
||||
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
|
||||
|
||||
@@ -95,6 +128,11 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
$input_json = json_decode($input, true);
|
||||
|
||||
foreach($input_json['videos'] as $element) {
|
||||
if($this->getInput('exclude_trailers') && $element['platform'] == 'EXTRAIT') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$durationSeconds = $element['durationSeconds'];
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $element['url'];
|
||||
@@ -106,10 +144,10 @@ class Arte7Bridge extends BridgeAbstract {
|
||||
if(!empty($element['subtitle']))
|
||||
$item['title'] = $element['title'] . ' | ' . $element['subtitle'];
|
||||
|
||||
$item['duration'] = round((int)$element['durationSeconds'] / 60);
|
||||
$durationMinutes = round((int)$durationSeconds / 60);
|
||||
$item['content'] = $element['teaserText']
|
||||
. '<br><br>'
|
||||
. $item['duration']
|
||||
. $durationMinutes
|
||||
. 'min<br><a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
|
@@ -20,13 +20,14 @@ class AsahiShimbunAJWBridge extends BridgeAbstract {
|
||||
'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',
|
||||
'Asia » China' => 'asia_world/china',
|
||||
'Asia » Korean Peninsula' => 'asia_world/korean_peninsula',
|
||||
'Asia » Around Asia' => 'asia_world/around_asia',
|
||||
'Asia » World' => 'asia_world/world',
|
||||
'Opinion » Editorial' => 'opinion/editorial',
|
||||
'Opinion » Vox Populi' => 'opinion/vox',
|
||||
),
|
||||
'defaultValue' => 'Politics',
|
||||
'defaultValue' => 'politics',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@@ -10,7 +10,8 @@ class AskfmBridge extends BridgeAbstract {
|
||||
'Ask.fm username' => array(
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'ApprovedAndReal'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
270
bridges/AssociatedPressNewsBridge.php
Normal file
270
bridges/AssociatedPressNewsBridge.php
Normal file
@@ -0,0 +1,270 @@
|
||||
<?php
|
||||
class AssociatedPressNewsBridge extends BridgeAbstract {
|
||||
const NAME = 'Associated Press News Bridge';
|
||||
const URI = 'https://apnews.com/';
|
||||
const DESCRIPTION = 'Returns newest articles by topic';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'Standard Topics' => array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'AP Top News' => 'apf-topnews',
|
||||
'Sports' => 'apf-sports',
|
||||
'Entertainment' => 'apf-entertainment',
|
||||
'Oddities' => 'apf-oddities',
|
||||
'Travel' => 'apf-Travel',
|
||||
'Technology' => 'apf-technology',
|
||||
'Lifestyle' => 'apf-lifestyle',
|
||||
'Business' => 'apf-business',
|
||||
'U.S. News' => 'apf-usnews',
|
||||
'Health' => 'apf-Health',
|
||||
'Science' => 'apf-science',
|
||||
'World News' => 'apf-WorldNews',
|
||||
'Politics' => 'apf-politics',
|
||||
'Religion' => 'apf-religion',
|
||||
'Photo Galleries' => 'PhotoGalleries',
|
||||
'Fact Checks' => 'APFactCheck',
|
||||
'Videos' => 'apf-videos',
|
||||
),
|
||||
'defaultValue' => 'apf-topnews',
|
||||
),
|
||||
),
|
||||
'Custom Topic' => array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'europe'
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 900; // 15 mins
|
||||
|
||||
private $detectParamRegex = '/^https?:\/\/(?:www\.)?apnews\.com\/(?:[tag|hub]+\/)?([\w-]+)$/';
|
||||
private $tagEndpoint = 'https://afs-prod.appspot.com/api/v2/feed/tag?tags=';
|
||||
private $feedName = '';
|
||||
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
if(preg_match($this->detectParamRegex, $url, $matches) > 0) {
|
||||
$params['topic'] = $matches[1];
|
||||
$params['context'] = 'Custom Topic';
|
||||
return $params;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
switch($this->getInput('topic')) {
|
||||
case 'Podcasts':
|
||||
returnClientError('Podcasts topic feed is not supported');
|
||||
break;
|
||||
case 'PressReleases':
|
||||
returnClientError('PressReleases topic feed is not supported');
|
||||
break;
|
||||
default:
|
||||
$this->collectCardData();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('topic'))) {
|
||||
return self::URI . $this->getInput('topic');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if (!empty($this->feedName)) {
|
||||
return $this->feedName . ' - Associated Press';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getTagURI() {
|
||||
if (!is_null($this->getInput('topic'))) {
|
||||
return $this->tagEndpoint . $this->getInput('topic');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
private function collectCardData() {
|
||||
$json = getContents($this->getTagURI())
|
||||
or returnServerError('Could not request: ' . $this->getTagURI());
|
||||
|
||||
$tagContents = json_decode($json, true);
|
||||
|
||||
if (empty($tagContents['tagObjs'])) {
|
||||
returnClientError('Topic not found: ' . $this->getInput('topic'));
|
||||
}
|
||||
|
||||
$this->feedName = $tagContents['tagObjs'][0]['name'];
|
||||
|
||||
foreach ($tagContents['cards'] as $card) {
|
||||
$item = array();
|
||||
|
||||
// skip hub peeks & Notifications
|
||||
if ($card['cardType'] == 'Hub Peek' || $card['cardType'] == 'Notification') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$storyContent = $card['contents'][0];
|
||||
|
||||
switch($storyContent['contentType']) {
|
||||
case 'web': // Skip link only content
|
||||
continue 2;
|
||||
|
||||
case 'video':
|
||||
$html = $this->processVideo($storyContent);
|
||||
|
||||
$item['enclosures'][] = 'https://storage.googleapis.com/afs-prod/media/'
|
||||
. $storyContent['media'][0]['id'] . '/800.jpeg';
|
||||
break;
|
||||
default:
|
||||
if (empty($storyContent['storyHTML'])) { // Skip if no storyHTML
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$html = defaultLinkTo($storyContent['storyHTML'], self::URI);
|
||||
$html = str_get_html($html);
|
||||
|
||||
$this->processMediaPlaceholders($html, $storyContent['id']);
|
||||
$this->processHubLinks($html, $storyContent);
|
||||
$this->processIframes($html);
|
||||
|
||||
if (!is_null($storyContent['leadPhotoId'])) {
|
||||
$item['enclosures'][] = 'https://storage.googleapis.com/afs-prod/media/'
|
||||
. $storyContent['leadPhotoId'] . '/800.jpeg';
|
||||
}
|
||||
}
|
||||
|
||||
$item['title'] = $card['contents'][0]['headline'];
|
||||
$item['uri'] = self::URI . $card['shortId'];
|
||||
|
||||
if ($card['contents'][0]['localLinkUrl']) {
|
||||
$item['uri'] = $card['contents'][0]['localLinkUrl'];
|
||||
}
|
||||
|
||||
$item['timestamp'] = $storyContent['published'];
|
||||
|
||||
if (is_null($storyContent['bylines']) === false) {
|
||||
// Remove 'By' from the bylines
|
||||
if (substr($storyContent['bylines'], 0, 2) == 'By') {
|
||||
$item['author'] = ltrim($storyContent['bylines'], 'By ');
|
||||
} else {
|
||||
$item['author'] = $storyContent['bylines'];
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = $html;
|
||||
|
||||
foreach ($storyContent['tagObjs'] as $tag) {
|
||||
$item['categories'][] = $tag['name'];
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 15) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processMediaPlaceholders($html, $id) {
|
||||
|
||||
if ($html->find('div.media-placeholder', 0)) {
|
||||
// Fetch page content
|
||||
$json = getContents('https://afs-prod.appspot.com/api/v2/content/' . $id);
|
||||
$storyContent = json_decode($json, true);
|
||||
|
||||
foreach ($html->find('div.media-placeholder') as $div) {
|
||||
$key = array_search($div->id, $storyContent['mediumIds']);
|
||||
|
||||
if (!isset($storyContent['media'][$key])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$media = $storyContent['media'][$key];
|
||||
|
||||
if ($media['type'] === 'Photo') {
|
||||
$mediaUrl = $media['gcsBaseUrl'] . $media['imageRenderedSizes'][0] . $media['imageFileExtension'];
|
||||
$mediaCaption = $media['caption'];
|
||||
|
||||
$div->outertext = <<<EOD
|
||||
<figure><img loading="lazy" src="{$mediaUrl}"/><figcaption>{$mediaCaption}</figcaption></figure>
|
||||
EOD;
|
||||
}
|
||||
|
||||
if ($media['type'] === 'YouTube') {
|
||||
$div->outertext = <<<EOD
|
||||
<iframe src="https://www.youtube.com/embed/{$media['externalId']}" width="560" height="315">
|
||||
</iframe>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Create full coverage links (HubLinks)
|
||||
*/
|
||||
private function processHubLinks($html, $storyContent) {
|
||||
|
||||
if (!empty($storyContent['richEmbeds'])) {
|
||||
foreach ($storyContent['richEmbeds'] as $embed) {
|
||||
|
||||
if ($embed['type'] === 'Hub Link') {
|
||||
$url = self::URI . $embed['tag']['id'];
|
||||
$div = $html->find('div[id=' . $embed['id'] . ']', 0);
|
||||
|
||||
if ($div) {
|
||||
$div->outertext = <<<EOD
|
||||
<p><a href="{$url}">{$embed['calloutText']} {$embed['displayName']}</a></p>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processVideo($storyContent) {
|
||||
$video = $storyContent['media'][0];
|
||||
|
||||
if ($video['type'] === 'YouTube') {
|
||||
$url = 'https://www.youtube.com/embed/' . $video['externalId'];
|
||||
$html = <<<EOD
|
||||
<iframe width="560" height="315" src="{$url}" frameborder="0" allowfullscreen></iframe>
|
||||
EOD;
|
||||
} else {
|
||||
$html = <<<EOD
|
||||
<video controls poster="https://storage.googleapis.com/afs-prod/media/{$video['id']}/800.jpeg" preload="none">
|
||||
<source src="{$video['gcsBaseUrl']} {$video['videoRenderedSizes'][0]} {$video['videoFileExtension']}" type="video/mp4">
|
||||
</video>
|
||||
EOD;
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
// Remove datawrapper.dwcdn.net iframes and related javaScript
|
||||
private function processIframes($html) {
|
||||
|
||||
foreach ($html->find('iframe') as $index => $iframe) {
|
||||
if (preg_match('/datawrapper\.dwcdn\.net/', $iframe->src)) {
|
||||
$iframe->outertext = '';
|
||||
|
||||
if ($html->find('script', $index)) {
|
||||
$html->find('script', $index)->outertext = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -8,7 +8,8 @@ class AtmoOccitanieBridge extends BridgeAbstract {
|
||||
const PARAMETERS = array(array(
|
||||
'city' => array(
|
||||
'name' => 'Ville',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'cahors'
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 7200;
|
||||
|
@@ -10,7 +10,7 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
|
||||
'name' => 'Series ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '12345'
|
||||
'exampleValue' => '188066'
|
||||
)
|
||||
),
|
||||
'By list' => array(
|
||||
@@ -18,7 +18,7 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
|
||||
'name' => 'List ID and Type',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '123456&list=read'
|
||||
'exampleValue' => '4395&list=read'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@@ -11,7 +11,8 @@ class BandcampBridge extends BridgeAbstract {
|
||||
'tag' => array(
|
||||
'name' => 'tag',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'hip-hop-rap'
|
||||
)
|
||||
),
|
||||
'By band' => array(
|
||||
@@ -19,7 +20,8 @@ class BandcampBridge extends BridgeAbstract {
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the band page URL',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'aesoprock'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
@@ -34,6 +36,7 @@ class BandcampBridge extends BridgeAbstract {
|
||||
'limit' => array(
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'title' => 'Number of releases to return',
|
||||
'defaultValue' => 5
|
||||
)
|
||||
@@ -67,13 +70,15 @@ class BandcampBridge extends BridgeAbstract {
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the album page URL',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'aesoprock'
|
||||
),
|
||||
'album' => array(
|
||||
'name' => 'album',
|
||||
'type' => 'text',
|
||||
'title' => 'Album name as seen in the album page URL',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'appleseed'
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
|
160
bridges/BandcampDailyBridge.php
Normal file
160
bridges/BandcampDailyBridge.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
class BandcampDailyBridge extends BridgeAbstract {
|
||||
const NAME = 'Bandcamp Daily Bridge';
|
||||
const URI = 'https://daily.bandcamp.com';
|
||||
const DESCRIPTION = 'Returns newest articles';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(
|
||||
'Latest articles' => array(),
|
||||
'Best of' => array(
|
||||
'best-content' => array(
|
||||
'name' => 'content',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Best Ambient' => 'best-ambient',
|
||||
'Best Beat Tapes' => 'best-beat-tapes',
|
||||
'Best Dance 12\'s' => 'best-dance-12s',
|
||||
'Best Contemporary Classical' => 'best-contemporary-classical',
|
||||
'Best Electronic' => 'best-electronic',
|
||||
'Best Experimental' => 'best-experimental',
|
||||
'Best Hip-Hop' => 'best-hip-hop',
|
||||
'Best Jazz' => 'best-jazz',
|
||||
'Best Metal' => 'best-metal',
|
||||
'Best Punk' => 'best-punk',
|
||||
'Best Reissues' => 'best-reissues',
|
||||
'Best Soul' => 'best-soul',
|
||||
),
|
||||
'defaultValue' => 'best-ambient',
|
||||
),
|
||||
),
|
||||
'Genres' => array(
|
||||
'genres-content' => array(
|
||||
'name' => 'content',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Acoustic' => 'genres/acoustic',
|
||||
'Alternative' => 'genres/alternative',
|
||||
'Ambient' => 'genres/ambient',
|
||||
'Blues' => 'genres/blues',
|
||||
'Classical' => 'genres/classical',
|
||||
'Comedy' => 'genres/comedy',
|
||||
'Country' => 'genres/country',
|
||||
'Devotional' => 'genres/devotional',
|
||||
'Electronic' => 'genres/electronic',
|
||||
'Experimental' => 'genres/experimental',
|
||||
'Folk' => 'genres/folk',
|
||||
'Funk' => 'genres/funk',
|
||||
'Hip-Hop/Rap' => 'genres/hip-hop-rap',
|
||||
'Jazz' => 'genres/jazz',
|
||||
'Kids' => 'genres/kids',
|
||||
'Latin' => 'genres/latin',
|
||||
'Metal' => 'genres/metal',
|
||||
'Pop' => 'genres/pop',
|
||||
'Punk' => 'genres/punk',
|
||||
'R&B/Soul' => 'genres/r-b-soul',
|
||||
'Reggae' => 'genres/reggae',
|
||||
'Rock' => 'genres/rock',
|
||||
'Soundtrack' => 'genres/soundtrack',
|
||||
'Spoken Word' => 'genres/spoken-word',
|
||||
'World' => 'genres/world',
|
||||
),
|
||||
'defaultValue' => 'genres/acoustic',
|
||||
),
|
||||
),
|
||||
'Franchises' => array(
|
||||
'franchises-content' => array(
|
||||
'name' => 'content',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Lists' => 'lists',
|
||||
'Features' => 'features',
|
||||
'Album of the Day' => 'album-of-the-day',
|
||||
'Acid Test' => 'acid-test',
|
||||
'Bandcamp Navigator' => 'bandcamp-navigator',
|
||||
'Big Ups' => 'big-ups',
|
||||
'Certified' => 'certified',
|
||||
'Gallery' => 'gallery',
|
||||
'Hidden Gems' => 'hidden-gems',
|
||||
'High Scores' => 'high-scores',
|
||||
'Label Profile' => 'label-profile',
|
||||
'Lifetime Achievement' => 'lifetime-achievement',
|
||||
'Scene Report' => 'scene-report',
|
||||
'Seven Essential Releases' => 'seven-essential-releases',
|
||||
'The Merch Table' => 'the-merch-table',
|
||||
),
|
||||
'defaultValue' => 'lists',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$articles = $html->find('articles-list', 0);
|
||||
|
||||
foreach($articles->find('div.list-article') as $index => $article) {
|
||||
$item = array();
|
||||
|
||||
$articlePath = $article->find('a.title', 0)->href;
|
||||
|
||||
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, 3600)
|
||||
or returnServerError('Could not request: ' . $articlePath);
|
||||
|
||||
$item['uri'] = $articlePath;
|
||||
$item['title'] = $articlePageHtml->find('article-title', 0)->innertext;
|
||||
$item['author'] = $articlePageHtml->find('article-credits > a', 0)->innertext;
|
||||
$item['content'] = html_entity_decode($articlePageHtml->find('meta[name="description"]', 0)->content, ENT_QUOTES);
|
||||
$item['timestamp'] = $articlePageHtml->find('meta[property="article:published_time"]', 0)->content;
|
||||
$item['categories'][] = $articlePageHtml->find('meta[property="article:section"]', 0)->content;
|
||||
|
||||
if ($articlePageHtml->find('meta[property="article:tag"]', 0)) {
|
||||
$item['categories'][] = $articlePageHtml->find('meta[property="article:tag"]', 0)->content;
|
||||
}
|
||||
|
||||
$item['enclosures'][] = $articlePageHtml->find('meta[name="twitter:image"]', 0)->content;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Latest articles':
|
||||
return self::URI . '/latest';
|
||||
case 'Best of':
|
||||
case 'Genres':
|
||||
case 'Franchises':
|
||||
// TODO Switch to array_key_first once php >= 7.3
|
||||
$contentKey = key(self::PARAMETERS[$this->queriedContext]);
|
||||
return self::URI . '/' . $this->getInput($contentKey);
|
||||
default:
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Latest articles':
|
||||
return $this->queriedContext . ' - Bandcamp Daily';
|
||||
case 'Best of':
|
||||
case 'Genres':
|
||||
case 'Franchises':
|
||||
// TODO Switch to array_key_first once php >= 7.3
|
||||
$contentKey = key(self::PARAMETERS[$this->queriedContext]);
|
||||
$contentValues = array_flip(self::PARAMETERS[$this->queriedContext][$contentKey]['values']);
|
||||
|
||||
return $contentValues[$this->getInput($contentKey)] . ' - Bandcamp Daily';
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,41 +1,18 @@
|
||||
<?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 NAME = 'Binance Blog';
|
||||
const URI = 'https://www.binance.com/en/blog';
|
||||
const DESCRIPTION = 'Subscribe to the Binance blog.';
|
||||
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());
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not fetch Binance blog data.');
|
||||
|
||||
$appData = $html->find('script[id="__APP_DATA"]');
|
||||
$appDataJson = json_decode($appData[0]->innertext);
|
||||
@@ -61,37 +38,4 @@ class BinanceBridge extends BridgeAbstract {
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
1458
bridges/BookMyShowBridge.php
Normal file
1458
bridges/BookMyShowBridge.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,35 +1,61 @@
|
||||
<?php
|
||||
require_once('GelbooruBridge.php');
|
||||
|
||||
class BooruprojectBridge extends GelbooruBridge {
|
||||
class BooruprojectBridge extends DanbooruBridge {
|
||||
|
||||
const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Booruproject';
|
||||
const URI = 'http://booru.org/';
|
||||
const URI = 'https://booru.org/';
|
||||
const DESCRIPTION = 'Returns images from given page of booruproject';
|
||||
const PARAMETERS = array(
|
||||
'global' => array(
|
||||
'p' => array(
|
||||
'name' => 'page',
|
||||
'defaultValue' => 0,
|
||||
'type' => 'number'
|
||||
),
|
||||
't' => array(
|
||||
'name' => 'tags'
|
||||
'name' => 'tags',
|
||||
'required' => true,
|
||||
'exampleValue' => 'tagme',
|
||||
'title' => 'Use "all" to get all posts'
|
||||
)
|
||||
),
|
||||
'Booru subdomain (subdomain.booru.org)' => array(
|
||||
'i' => array(
|
||||
'name' => 'Subdomain',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'rm'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const PATHTODATA = '.thumb';
|
||||
const IDATTRIBUTE = 'id';
|
||||
const TAGATTRIBUTE = 'title';
|
||||
const PIDBYPAGE = 20;
|
||||
|
||||
protected function getFullURI(){
|
||||
return $this->getURI()
|
||||
. 'index.php?page=post&s=list&pid='
|
||||
. ($this->getInput('p') ? ($this->getInput('p') - 1) * static::PIDBYPAGE : '')
|
||||
. '&tags=' . urlencode($this->getInput('t'));
|
||||
}
|
||||
|
||||
protected function getTags($element){
|
||||
$tags = parent::getTags($element);
|
||||
$tags = explode(' ', $tags);
|
||||
|
||||
// Remove statistics from the tags list (identified by colon)
|
||||
foreach($tags as $key => $tag) {
|
||||
if(strpos($tag, ':') !== false) unset($tags[$key]);
|
||||
}
|
||||
|
||||
return implode(' ', $tags);
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('i'))) {
|
||||
return 'http://' . $this->getInput('i') . '.booru.org/';
|
||||
return 'https://' . $this->getInput('i') . '.booru.org/';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
|
@@ -2,7 +2,7 @@
|
||||
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 DESCRIPTION = 'Returns 10 newest videos by category and edition';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
@@ -38,9 +38,7 @@ class BrutBridge extends BridgeAbstract {
|
||||
|
||||
const CACHE_TIMEOUT = 1800; // 30 mins
|
||||
|
||||
private $videoId = '';
|
||||
private $videoType = '';
|
||||
private $videoImage = '';
|
||||
private $jsonRegex = '/window\.__PRELOADED_STATE__ = ((?:.*)});/';
|
||||
|
||||
public function collectData() {
|
||||
|
||||
@@ -48,36 +46,38 @@ class BrutBridge extends BridgeAbstract {
|
||||
|
||||
$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) {
|
||||
foreach($results->find('li.col-6.col-sm-4.col-md-3.col-lg-2.px-2.pb-4') as $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);
|
||||
$json = $this->extractJson($videoPageHtml);
|
||||
$id = array_keys((array) $json->media->index)[0];
|
||||
|
||||
$item['uri'] = $videoPath;
|
||||
$item['title'] = $description->find('h1', 0)->plaintext;
|
||||
$item['title'] = $json->media->index->$id->title;
|
||||
$item['timestamp'] = $json->media->index->$id->published_at;
|
||||
$item['enclosures'][] = $json->media->index->$id->media->thumbnail;
|
||||
|
||||
if ($description->find('div.date', 0)->children(0)) {
|
||||
$description->find('div.date', 0)->children(0)->outertext = '';
|
||||
$description = $json->media->index->$id->description;
|
||||
$article = '';
|
||||
|
||||
if (is_null($json->media->index->$id->media->seo_article) === false) {
|
||||
$article = markdownToHtml($json->media->index->$id->media->seo_article);
|
||||
}
|
||||
|
||||
$item['content'] = $this->processContent(
|
||||
$description
|
||||
);
|
||||
|
||||
$item['timestamp'] = $this->processDate($description);
|
||||
$item['enclosures'][] = $this->videoImage;
|
||||
$item['content'] = <<<EOD
|
||||
<video controls poster="{$json->media->index->$id->media->thumbnail}" preload="none">
|
||||
<source src="{$json->media->index->$id->media->mp4_url}" type="video/mp4">
|
||||
</video>
|
||||
<p>{$description}</p>
|
||||
{$article}
|
||||
EOD;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 5) {
|
||||
if (count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -107,51 +107,21 @@ class BrutBridge extends BridgeAbstract {
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function processDate($description) {
|
||||
/**
|
||||
* Extract JSON from page
|
||||
*/
|
||||
private function extractJson($html) {
|
||||
|
||||
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'));
|
||||
if (!preg_match($this->jsonRegex, $html, $parts)) {
|
||||
returnServerError('Failed to extract data from page');
|
||||
}
|
||||
|
||||
return strtotime($description->find('div.date', 0)->innertext);
|
||||
}
|
||||
$data = json_decode($parts[1]);
|
||||
|
||||
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>';
|
||||
if ($data === false) {
|
||||
returnServerError('Failed to decode extracted data');
|
||||
}
|
||||
|
||||
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);
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
0
bridges/BukowskisBridge.php
Executable file → Normal file
0
bridges/BukowskisBridge.php
Executable file → Normal file
89
bridges/BundestagParteispendenBridge.php
Normal file
89
bridges/BundestagParteispendenBridge.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
class BundestagParteispendenBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'mibe';
|
||||
const NAME = 'Deutscher Bundestag - Parteispenden';
|
||||
const URI = 'https://www.bundestag.de/parlament/praesidium/parteienfinanzierung/fundstellen50000';
|
||||
|
||||
const CACHE_TIMEOUT = 86400; // 24h
|
||||
const DESCRIPTION = 'Returns the latest "soft money" donations to parties represented in the German Bundestag.';
|
||||
const CONTENT_TEMPLATE = <<<TMPL
|
||||
<p><b>Partei:</b><br>%s</p>
|
||||
<p><b>Spendenbetrag:</b><br>%s</p>
|
||||
<p><b>Spender:</b><br>%s</p>
|
||||
<p><b>Eingang der Spende:</b><br>%s</p>
|
||||
TMPL;
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.bundestag.de/static/appdata/includes/images/layout/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$ajaxUri = <<<URI
|
||||
https://www.bundestag.de/ajax/filterlist/de/parlament/praesidium/parteienfinanzierung/fundstellen50000/462002-462002
|
||||
URI;
|
||||
// Get the main page
|
||||
$html = getSimpleHTMLDOMCached($ajaxUri, self::CACHE_TIMEOUT)
|
||||
or returnServerError('Could not request AJAX list.');
|
||||
|
||||
// Build the URL from the first anchor element. The list is sorted by year, descending, so the first element is the current year.
|
||||
$firstAnchor = $html->find('a', 0)
|
||||
or returnServerError('Could not find the proper HTML element.');
|
||||
|
||||
$url = 'https://www.bundestag.de' . $firstAnchor->href;
|
||||
|
||||
// Get the actual page with the soft money donations
|
||||
$html = getSimpleHTMLDOMCached($url, self::CACHE_TIMEOUT)
|
||||
or returnServerError('Could not request ' . $url);
|
||||
|
||||
$rows = $html->find('table.table > tbody > tr')
|
||||
or returnServerError('Could not find the proper HTML elements.');
|
||||
|
||||
foreach($rows as $row) {
|
||||
$item = $this->generateItemFromRow($row);
|
||||
if (is_array($item)) {
|
||||
$item['uri'] = $url;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function generateItemFromRow(simple_html_dom_node $row)
|
||||
{
|
||||
// The row must have 5 columns. There are monthly header rows, which are ignored here.
|
||||
if(count($row->children) != 5)
|
||||
return null;
|
||||
|
||||
$item = array();
|
||||
|
||||
// | column | paragraph inside column
|
||||
$party = $row->children[0]->children[0]->innertext;
|
||||
$amount = $row->children[1]->children[0]->innertext . ' €';
|
||||
$donor = $row->children[2]->children[0]->innertext;
|
||||
$date = $row->children[3]->children[0]->innertext;
|
||||
$dip = $row->children[4]->children[0]->find('a.dipLink', 0);
|
||||
|
||||
// Strip whitespace from date string.
|
||||
$date = str_replace(' ', '', $date);
|
||||
|
||||
$content = sprintf(self::CONTENT_TEMPLATE, $party, $amount, $donor, $date);
|
||||
|
||||
$item = array(
|
||||
'title' => $party . ': ' . $amount,
|
||||
'content' => $content,
|
||||
'uid' => sha1($content),
|
||||
);
|
||||
|
||||
// Try to get the link to the official document
|
||||
if ($dip != null)
|
||||
$item['enclosures'] = array($dip->href);
|
||||
|
||||
// Try to parse the date
|
||||
$dateTime = DateTime::createFromFormat('d.m.Y', $date);
|
||||
if ($dateTime !== false)
|
||||
$item['timestamp'] = $dateTime->getTimestamp();
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
36
bridges/CBCEditorsBlogBridge.php
Normal file
36
bridges/CBCEditorsBlogBridge.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
class CBCEditorsBlogBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'quickwick';
|
||||
const NAME = 'CBC Editors Blog';
|
||||
const URI = 'https://www.cbc.ca/news/editorsblog';
|
||||
const DESCRIPTION = 'Recent CBC Editor\'s Blog posts';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
// Loop on each blog post entry
|
||||
foreach($html->find('div.contentListCards', 0)->find('a[data-test=type-story]') as $element) {
|
||||
$headline = ($element->find('.headline', 0))->innertext;
|
||||
$timestamp = ($element->find('time', 0))->datetime;
|
||||
$articleUri = 'https://www.cbc.ca' . $element->href;
|
||||
$summary = ($element->find('div.description', 0))->innertext;
|
||||
$thumbnailUris = ($element->find('img[loading=lazy]', 0))->srcset;
|
||||
$thumbnailUri = rtrim(explode(',', $thumbnailUris)[0], ' 300w');
|
||||
|
||||
// Fill item
|
||||
$item = array();
|
||||
$item['uri'] = $articleUri;
|
||||
$item['id'] = $item['uri'];
|
||||
$item['timestamp'] = $timestamp;
|
||||
$item['title'] = $headline;
|
||||
$item['content'] = '<img src="'
|
||||
. $thumbnailUri . '" /><br>' . $summary;
|
||||
$item['author'] = 'Editor\'s Blog';
|
||||
|
||||
if(isset($item['title'])) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -12,13 +12,13 @@ class CNETFranceBridge extends FeedExpander
|
||||
'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'
|
||||
'exampleValue' => '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'
|
||||
'exampleValue' => 'bon-plan;bons-plans'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
41
bridges/CarThrottleBridge.php
Normal file
41
bridges/CarThrottleBridge.php
Normal file
@@ -0,0 +1,41 @@
|
||||
<?php
|
||||
class CarThrottleBridge extends FeedExpander {
|
||||
const NAME = 'Car Throttle ';
|
||||
const URI = 'https://www.carthrottle.com';
|
||||
const DESCRIPTION = 'Get the latest car-related news from Car Throttle.';
|
||||
const MAINTAINER = 't0stiman';
|
||||
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas('https://www.carthrottle.com/rss', 10);
|
||||
}
|
||||
|
||||
protected function parseItem($feedItem) {
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
//fetch page
|
||||
$articlePage = getSimpleHTMLDOMCached($feedItem->link)
|
||||
or returnServerError('Could not retrieve ' . $feedItem->link);
|
||||
|
||||
$subtitle = $articlePage->find('p.standfirst', 0);
|
||||
$article = $articlePage->find('div.content_field', 0);
|
||||
|
||||
$item['content'] = str_get_html($subtitle . $article);
|
||||
|
||||
//convert <iframe>s to <a>s. meant for embedded videos.
|
||||
foreach($item['content']->find('iframe') as $found) {
|
||||
|
||||
$iframeUrl = $found->getAttribute('src');
|
||||
|
||||
if ($iframeUrl) {
|
||||
$found->outertext = '<a href="' . $iframeUrl . '">' . $iframeUrl . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
//remove scripts from the text
|
||||
foreach ($item['content']->find('script') as $remove) {
|
||||
$remove->outertext = '';
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -13,8 +13,8 @@ class CastorusBridge extends BridgeAbstract {
|
||||
'name' => 'ZIP code',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '74910, 74',
|
||||
'title' => 'Insert ZIP code (complete or partial)'
|
||||
'exampleValue' => '7',
|
||||
'title' => 'Insert ZIP code (complete or partial). e.g: 78125 OR 781 OR 7'
|
||||
)
|
||||
),
|
||||
'Get latest changes via city name' => array(
|
||||
@@ -22,8 +22,8 @@ class CastorusBridge extends BridgeAbstract {
|
||||
'name' => 'City name',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'Seyssel, Seys',
|
||||
'title' => 'Insert city name (complete or partial)'
|
||||
'exampleValue' => 'Paris',
|
||||
'title' => 'Insert city name (complete or partial). e.g: Paris OR Par OR P'
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -35,7 +35,7 @@ class CastorusBridge extends BridgeAbstract {
|
||||
if(!$title)
|
||||
returnServerError('Cannot find title!');
|
||||
|
||||
return htmlspecialchars(trim($title->plaintext));
|
||||
return trim($title->plaintext);
|
||||
}
|
||||
|
||||
// Extracts the url from an actitiy
|
||||
|
60
bridges/CdactionBridge.php
Normal file
60
bridges/CdactionBridge.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class CdactionBridge extends BridgeAbstract {
|
||||
const NAME = 'CD-ACTION bridge';
|
||||
const URI = 'https://cdaction.pl';
|
||||
const DESCRIPTION = 'Fetches the latest posts from given category.';
|
||||
const MAINTAINER = 'tomaszkane';
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
'name' => 'Kategoria',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Najnowsze (wszystkie)' => 'najnowsze',
|
||||
'Newsy' => 'newsy',
|
||||
'Recenzje' => 'recenzje',
|
||||
'Teksty' => array(
|
||||
'Publicystyka' => 'publicystyka',
|
||||
'Zapowiedzi' => 'zapowiedzi',
|
||||
'Już graliśmy' => 'juz-gralismy',
|
||||
'Poradniki' => 'poradniki',
|
||||
),
|
||||
'Kultura' => 'kultura',
|
||||
'Wideo' => 'wideo',
|
||||
'Czasopismo' => 'czasopismo',
|
||||
'Technologie' => array(
|
||||
'Artykuły' => 'artykuly',
|
||||
'Testy' => 'testy',
|
||||
),
|
||||
'Na luzie' => array(
|
||||
'Konkursy' => 'konkursy',
|
||||
'Nadgodziny' => 'nadgodziny',
|
||||
)
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/' . $this->getInput('category'));
|
||||
|
||||
$newsJson = $html->find('script#__NEXT_DATA__', 0)->innertext;
|
||||
if (!$newsJson = json_decode($newsJson)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$queriesIndex = $this->getInput('category') === 'najnowsze' ? 0 : 1;
|
||||
foreach ($newsJson->props->pageProps->dehydratedState->queries[$queriesIndex]->state->data->results as $news) {
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '/' . $news->category->slug . '/' . $news->slug;
|
||||
$item['title'] = $news->title;
|
||||
$item['timestamp'] = $news->publishedAt;
|
||||
$item['author'] = $news->editor->fullName;
|
||||
$item['content'] = $news->lead;
|
||||
$item['enclosures'][] = $news->bannerUrl;
|
||||
$item['categories'] = array_column($news->tags, 'name');
|
||||
$item['uid'] = $news->id;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -20,6 +20,7 @@ class CodebergBridge extends BridgeAbstract {
|
||||
'name' => 'Issue ID',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '513',
|
||||
)
|
||||
),
|
||||
'Pull Requests' => array(),
|
||||
@@ -28,14 +29,14 @@ class CodebergBridge extends BridgeAbstract {
|
||||
'username' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'username',
|
||||
'exampleValue' => 'Codeberg',
|
||||
'title' => 'Username of account that the repository belongs to.',
|
||||
'required' => true,
|
||||
),
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'repo',
|
||||
'exampleValue' => 'Community',
|
||||
'required' => true,
|
||||
)
|
||||
)
|
||||
@@ -205,6 +206,9 @@ class CodebergBridge extends BridgeAbstract {
|
||||
return $this->getInput('username') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract commits
|
||||
*/
|
||||
private function extractCommits($html) {
|
||||
$table = $html->find('table#commits-table', 0);
|
||||
$tbody = $table->find('tbody.commit-list', 0);
|
||||
@@ -231,25 +235,27 @@ class CodebergBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract issues
|
||||
*/
|
||||
private function extractIssues($html) {
|
||||
$div = $html->find('div.repository', 0);
|
||||
$div = $html->find('div.issue.list', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = array();
|
||||
|
||||
$number = $li->find('div', 0)->plaintext;
|
||||
$number = trim($li->find('a.index,ml-0.mr-2', 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;
|
||||
$item['timestamp'] = $li->find('span.time-since', 0)->title;
|
||||
$item['author'] = $li->find('div.desc', 0)->find('a', 1)->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);
|
||||
$item['content'] = $issuePage->find('div.timeline-item.comment.first', 0)->find('div.render-content.markup', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
@@ -259,20 +265,23 @@ class CodebergBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract issue comments
|
||||
*/
|
||||
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) {
|
||||
foreach ($html->find('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['title'] = $this->ellipsisTitle($div->find('div.render-content.markup', 0)->plaintext);
|
||||
$item['uri'] = $div->find('span.text.grey', 0)->find('a', 1)->href;
|
||||
$item['content'] = $div->find('div.render-content.markdown', 0);
|
||||
$item['content'] = $div->find('div.render-content.markup', 0);
|
||||
|
||||
if ($div->find('div.dropzone-attachments', 0)) {
|
||||
$item['content'] .= $div->find('div.dropzone-attachments', 0);
|
||||
@@ -285,25 +294,27 @@ class CodebergBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract pulls
|
||||
*/
|
||||
private function extractPulls($html) {
|
||||
$div = $html->find('div.repository', 0);
|
||||
$div = $html->find('div.issue.list', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = array();
|
||||
|
||||
$number = $li->find('div', 0)->plaintext;
|
||||
$number = trim($li->find('a.index,ml-0.mr-2', 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;
|
||||
$item['timestamp'] = $li->find('span.time-since', 0)->title;
|
||||
$item['author'] = $li->find('div.desc', 0)->find('a', 1)->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);
|
||||
$item['content'] = $pullRequestPage->find('ui.timeline', 0)->find('div.render-content.markup', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
@@ -313,49 +324,40 @@ class CodebergBridge extends BridgeAbstract {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract releases
|
||||
*/
|
||||
private function extractReleases($html) {
|
||||
$ul = $html->find('ul#release-list', 0);
|
||||
|
||||
foreach ($ul->find('li.ui.grid') as $li) {
|
||||
$item = array();
|
||||
$item['title'] = $li->find('h4', 0)->plaintext;
|
||||
$item['uri'] = $li->find('h4', 0)->find('a', 0)->href;
|
||||
|
||||
if ($li->find('h3', 0)) { // Release
|
||||
$item['title'] = $li->find('h3', 0)->plaintext;
|
||||
$item['uri'] = $li->find('h3', 0)->find('a', 0)->href;
|
||||
$tag = $this->stripSvg($li->find('span.tag', 0));
|
||||
$commit = $this->stripSvg($li->find('span.commit', 0));
|
||||
$downloads = $this->extractDownloads($li->find('details.download', 0));
|
||||
|
||||
$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
|
||||
$item['content'] = $li->find('div.markup.desc', 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;
|
||||
}
|
||||
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('span.author', 0)->find('a', 0)->plaintext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract downloads for a releases
|
||||
*/
|
||||
private function extractDownloads($html, $skipFirst = false) {
|
||||
$downloads = '';
|
||||
|
||||
@@ -365,7 +367,7 @@ HTML;
|
||||
}
|
||||
|
||||
$downloads .= <<<HTML
|
||||
{$a}<br>
|
||||
<a href="{$a->herf}">{$a->plaintext}</a><br>
|
||||
HTML;
|
||||
}
|
||||
|
||||
@@ -375,6 +377,9 @@ HTML;
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ellipsis title to first 100 characters
|
||||
*/
|
||||
private function ellipsisTitle($text) {
|
||||
$length = 100;
|
||||
|
||||
@@ -384,4 +389,15 @@ EOD;
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip SVG tag
|
||||
*/
|
||||
private function stripSvg($html) {
|
||||
if ($html->find('svg', 0)) {
|
||||
$html->find('svg', 0)->outertext = '';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
@@ -3,13 +3,15 @@ class ComicsKingdomBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'stjohnjohnson';
|
||||
const NAME = 'Comics Kingdom Unofficial RSS';
|
||||
const URI = 'https://www.comicskingdom.com/';
|
||||
const URI = 'https://comicskingdom.com/';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Comics Kingdom Unofficial RSS';
|
||||
const PARAMETERS = array( array(
|
||||
'comicname' => array(
|
||||
'name' => 'comicname',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'mutts',
|
||||
'title' => 'The name of the comic in the URL after https://comicskingdom.com/',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
@@ -21,15 +23,13 @@ class ComicsKingdomBridge extends BridgeAbstract {
|
||||
$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++) {
|
||||
$link = $html->find('meta[property=og:url]', -1)->content;
|
||||
for($i = 0; $i < 3; $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);
|
||||
|
||||
@@ -41,6 +41,8 @@ class ComicsKingdomBridge extends BridgeAbstract {
|
||||
$item['content'] = '<img src="' . $imagelink . '" />';
|
||||
|
||||
$this->items[] = $item;
|
||||
$link = $page->find('div.comic-viewer-inline a', 0)->href;
|
||||
if (empty($link)) break; // allow bridge to continue if there's less than 3 comics
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,95 +0,0 @@
|
||||
<?php
|
||||
class ContainerLinuxReleasesBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const NAME = 'Core OS Container Linux Releases Bridge';
|
||||
const URI = 'https://coreos.com/releases/';
|
||||
const DESCRIPTION = 'Returns the releases notes for Container Linux';
|
||||
|
||||
const STABLE = 'stable';
|
||||
const BETA = 'beta';
|
||||
const ALPHA = 'alpha';
|
||||
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'channel' => array(
|
||||
'name' => 'Release Channel',
|
||||
'type' => 'list',
|
||||
'defaultValue' => self::STABLE,
|
||||
'values' => array(
|
||||
'Stable' => self::STABLE,
|
||||
'Beta' => self::BETA,
|
||||
'Alpha' => self::ALPHA,
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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 = array();
|
||||
|
||||
$item['uri'] = "https://coreos.com/releases/#$releaseVersion";
|
||||
$item['title'] = $releaseVersion;
|
||||
|
||||
$content = $release['release_notes'];
|
||||
$content .= <<<EOT
|
||||
|
||||
Major Software:
|
||||
* Kernel: {$release['major_software']['kernel'][0]}
|
||||
* Docker: {$release['major_software']['docker'][0]}
|
||||
* etcd: {$release['major_software']['etcd'][0]}
|
||||
EOT;
|
||||
$item['timestamp'] = strtotime($release['release_date']);
|
||||
|
||||
// Based on https://gist.github.com/jbroadway/2836900
|
||||
// Links
|
||||
$regex = '/\[([^\[]+)\]\(([^\)]+)\)/';
|
||||
$replacement = '<a href=\'\2\'>\1</a>';
|
||||
$item['content'] = preg_replace($regex, $replacement, $content);
|
||||
|
||||
// Headings
|
||||
$regex = '/^(.*)\:\s?$/m';
|
||||
$replacement = '<h3>\1</h3>';
|
||||
$item['content'] = preg_replace($regex, $replacement, $item['content']);
|
||||
|
||||
// Lists
|
||||
$regex = '/\n\s*[\*|\-](.*)/';
|
||||
$item['content'] = preg_replace_callback ($regex, function($regs) {
|
||||
$item = $regs[1];
|
||||
return sprintf ('<ul><li>%s</li></ul>', trim ($item));
|
||||
}, $item['content']);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getJsonUri() {
|
||||
$channel = $this->getInput('channel');
|
||||
|
||||
return "https://coreos.com/releases/releases-$channel.json";
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('channel'))) {
|
||||
return 'Container Linux Releases: ' . $this->getInput('channel') . ' Channel';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -15,11 +15,10 @@ class CourrierInternationalBridge extends FeedExpander {
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
$articlePage = getSimpleHTMLDOMCached($feedItem->link);
|
||||
$content = $articlePage->find('.article-text', 0);
|
||||
if(!$content) {
|
||||
$content = $articlePage->find('.depeche-text', 0);
|
||||
$content = $articlePage->find('.article-text, depeche-text', 0);
|
||||
if (!$content) {
|
||||
return $item;
|
||||
}
|
||||
|
||||
$item['content'] = sanitize($content);
|
||||
|
||||
return $item;
|
||||
|
107
bridges/CraigslistBridge.php
Normal file
107
bridges/CraigslistBridge.php
Normal file
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
class CraigslistBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'Yaman Qalieh';
|
||||
const NAME = 'Craigslist Bridge';
|
||||
const URI = 'https://craigslist.org/';
|
||||
const DESCRIPTION = 'Returns craigslist search results';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'region' => array(
|
||||
'name' => 'Region',
|
||||
'title' => 'The subdomain before craigslist.org in the URL',
|
||||
'exampleValue' => 'sfbay',
|
||||
'required' => true
|
||||
),
|
||||
'search' => array(
|
||||
'name' => 'Search Query',
|
||||
'title' => 'Everything in the URL after /search/',
|
||||
'exampleValue' => 'sya?query=laptop',
|
||||
'required' => true
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Number of Posts',
|
||||
'type' => 'number',
|
||||
'title' => 'The maximum number of posts is 120. Use 0 for unlimited posts.',
|
||||
'defaultValue' => '25'
|
||||
)
|
||||
));
|
||||
|
||||
const TEST_DETECT_PARAMETERS = array(
|
||||
'https://sfbay.craigslist.org/search/sya?query=laptop' => array(
|
||||
'region' => 'sfbay', 'search' => 'sya?query=laptop'
|
||||
),
|
||||
'https://newyork.craigslist.org/search/sss?query=32gb+flash+drive&bundleDuplicates=1&max_price=20' => array(
|
||||
'region' => 'newyork', 'search' => 'sss?query=32gb+flash+drive&bundleDuplicates=1&max_price=20'
|
||||
),
|
||||
);
|
||||
|
||||
const URL_REGEX = '/^https:\/\/(?<region>\w+).craigslist.org\/search\/(?<search>.+)/';
|
||||
|
||||
public function detectParameters($url) {
|
||||
if(preg_match(self::URL_REGEX, $url, $matches)) {
|
||||
$params = array();
|
||||
$params['region'] = $matches['region'];
|
||||
$params['search'] = $matches['search'];
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('region'))) {
|
||||
$domain = 'https://' . $this->getInput('region') . '.craigslist.org/search/';
|
||||
return urljoin($domain, $this->getInput('search'));
|
||||
}
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$uri = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
// Check if no results page is shown (nearby results)
|
||||
if ($html->find('.displaycountShow', 0)->plaintext == '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Search for "more from nearby areas" banner in order to skip those results
|
||||
$results = $html->find('.result-row, h4.nearby');
|
||||
|
||||
// Limit the number of posts
|
||||
if ($this->getInput('limit') > 0) {
|
||||
$results = array_slice($results, 0, $this->getInput('limit'));
|
||||
}
|
||||
|
||||
foreach($results as $post) {
|
||||
|
||||
// Skip "nearby results" banner and results
|
||||
// This only appears when searchNearby is not specified
|
||||
if ($post->tag == 'h4') {
|
||||
break;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
$heading = $post->find('.result-heading a', 0);
|
||||
$item['uri'] = $heading->href;
|
||||
$item['title'] = $heading->plaintext;
|
||||
$item['timestamp'] = $post->find('.result-date', 0)->datetime;
|
||||
$item['uid'] = $heading->id;
|
||||
$item['content'] = $post->find('.result-price', 0)->plaintext . ' '
|
||||
// Find the location (local and nearby results if searchNearby=1)
|
||||
. $post->find('.result-hood, span.nearby', 0)->plaintext;
|
||||
|
||||
$images = $post->find('.result-image[data-ids]', 0);
|
||||
if (!is_null($images)) {
|
||||
$item['content'] .= '<br>';
|
||||
foreach(explode(',', $images->getAttribute('data-ids')) as $image) {
|
||||
// Remove leading 3: from each image id
|
||||
$id = substr($image, 2);
|
||||
$image_uri = 'https://images.craigslist.org/' . $id . '_300x300.jpg';
|
||||
$item['content'] .= '<img src="' . $image_uri . '">';
|
||||
$item['enclosures'][] = $image_uri;
|
||||
}
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -4,41 +4,41 @@ class CryptomeBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'BoboTiG';
|
||||
const NAME = 'Cryptome';
|
||||
const URI = 'https://cryptome.org/';
|
||||
const CACHE_TIMEOUT = 21600; //6h
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
const DESCRIPTION = 'Returns the N most recent documents.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'n' => array(
|
||||
'name' => 'number of elements',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 20,
|
||||
'required' => true,
|
||||
'exampleValue' => 10
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . '/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$number = $this->getInput('n');
|
||||
|
||||
/* number of documents */
|
||||
if(!empty($number)) {
|
||||
$num = min($number, 20);
|
||||
}
|
||||
|
||||
foreach($html->find('pre') as $element) {
|
||||
for($i = 0; $i < $num; ++$i) {
|
||||
$i = 0;
|
||||
foreach($html->find('pre', 1)->find('b') as $element) {
|
||||
foreach($element->find('a') as $element1) {
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . substr($element->find('a', $i)->href, 20);
|
||||
$item['title'] = substr($element->find('b', $i)->plaintext, 22);
|
||||
$item['content'] = preg_replace(
|
||||
'#http://cryptome.org/#',
|
||||
self::URI,
|
||||
$element->find('b', $i)->innertext
|
||||
);
|
||||
$item['uri'] = $element1->href;
|
||||
$item['title'] = $element->plaintext;
|
||||
$this->items[] = $item;
|
||||
|
||||
if ($i > $num) {
|
||||
break 2;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
98
bridges/CubariBridge.php
Normal file
98
bridges/CubariBridge.php
Normal file
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
class CubariBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Cubari';
|
||||
const URI = 'https://cubari.moe';
|
||||
const DESCRIPTION = 'Parses given cubari-formatted JSON file for updates.';
|
||||
const MAINTAINER = 'KamaleiZestri';
|
||||
const PARAMETERS = array(array(
|
||||
'gist' => array(
|
||||
'name' => 'Gist/Raw Url',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://raw.githubusercontent.com/kurisumx/baka/main/ikedan'
|
||||
)
|
||||
));
|
||||
|
||||
private $mangaTitle = '';
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (!empty($this->mangaTitle))
|
||||
return $this->mangaTitle . ' - ' . self::NAME;
|
||||
else
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
if ($this->getInput('gist') != '')
|
||||
return self::URI . '/read/gist/' . $this->getEncodedGist();
|
||||
else
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
/**
|
||||
* The Cubari bridge.
|
||||
*
|
||||
* Cubari urls are base64 encodes of a given github raw or gist link described as below:
|
||||
* https://cubari.moe/read/gist/${bаse64.url_encode(raw/<rest of the url...>)}/
|
||||
* https://cubari.moe/read/gist/${bаse64.url_encode(gist/<rest of the url...>)}/
|
||||
* https://cubari.moe/read/gist/${gitio shortcode}
|
||||
*
|
||||
* This bridge uses just the raw/gist and generates matching cubari urls.
|
||||
*/
|
||||
public function collectData()
|
||||
{
|
||||
$jsonSite = getContents($this->getInput('gist'));
|
||||
$jsonFile = json_decode($jsonSite, true);
|
||||
|
||||
$this->mangaTitle = $jsonFile['title'];
|
||||
|
||||
$chapters = $jsonFile['chapters'];
|
||||
|
||||
foreach ($chapters as $chapnum => $chapter) {
|
||||
$item = $this->getItemFromChapter($chapnum, $chapter);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
array_multisort(array_column($this->items, 'timestamp'), SORT_DESC, $this->items);
|
||||
}
|
||||
|
||||
protected function getEncodedGist()
|
||||
{
|
||||
$url = $this->getInput('gist');
|
||||
|
||||
preg_match('/\/([a-z]*)\.githubusercontent.com(.*)/', $url, $matches);
|
||||
|
||||
// raw or gist is first match.
|
||||
$unencoded = $matches[1] . $matches[2];
|
||||
|
||||
return base64_encode($unencoded);
|
||||
}
|
||||
|
||||
private function getSanitizedHash($string)
|
||||
{
|
||||
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
|
||||
}
|
||||
|
||||
protected function getItemFromChapter($chapnum, $chapter)
|
||||
{
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI() . '/' . $chapnum;
|
||||
$item['title'] = 'Chapter ' . $chapnum . ' - ' . $chapter['title'] . ' - ' . $this->mangaTitle;
|
||||
foreach ($chapter['groups'] as $key => $value)
|
||||
$item['author'] = $key;
|
||||
$item['timestamp'] = $chapter['last_updated'];
|
||||
|
||||
$item['content'] = '<p>Manga: <a href=' . $this->getURI() . '>' . $this->mangaTitle . '</a> </p>
|
||||
<p>Chapter Number: ' . $chapnum . '</p>
|
||||
<p>Chapter Title: <a href=' . $item['uri'] . '>' . $chapter['title'] . '</a></p>
|
||||
<p>Group: ' . $item['author'] . '</p>';
|
||||
|
||||
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
37
bridges/CyanideAndHappinessBridge.php
Normal file
37
bridges/CyanideAndHappinessBridge.php
Normal file
@@ -0,0 +1,37 @@
|
||||
<?php
|
||||
class CyanideAndHappinessBridge extends BridgeAbstract {
|
||||
const NAME = 'Cyanide & Happiness';
|
||||
const URI = 'https://explosm.net/';
|
||||
const DESCRIPTION = 'The Webcomic from Explosm.';
|
||||
const MAINTAINER = 'sal0max';
|
||||
const CACHE_TIMEOUT = 60 * 60 * 2; // 2 hours
|
||||
|
||||
public function getIcon() {
|
||||
return self::URI . 'favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
return self::URI . 'comics/latest#comic';
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getUri());
|
||||
|
||||
foreach ($html->find('[class*=ComicImage]') as $element) {
|
||||
$date = $element->find('[class^=Author__Right] p', 0)->plaintext;
|
||||
$author = str_replace('by ', '', $element->find('[class^=Author__Right] p', 1)->plaintext);
|
||||
$image = $element->find('img', 0)->src;
|
||||
$link = $html->find('[rel=canonical]', 0)->href;
|
||||
|
||||
$item = array(
|
||||
'uid' => $link,
|
||||
'author' => $author,
|
||||
'title' => $date,
|
||||
'uri' => $link . '#comic',
|
||||
'timestamp' => str_replace('.', '-', $date) . 'T00:00:00Z',
|
||||
'content' => "<img src=\"$image\" />"
|
||||
);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -11,19 +11,22 @@ class DailymotionBridge extends BridgeAbstract {
|
||||
'By username' => array(
|
||||
'u' => array(
|
||||
'name' => 'username',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'moviepilot',
|
||||
)
|
||||
),
|
||||
'By playlist id' => array(
|
||||
'p' => array(
|
||||
'name' => 'playlist id',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'x6xyc6',
|
||||
)
|
||||
),
|
||||
'From search results' => array(
|
||||
's' => array(
|
||||
'name' => 'Search keyword',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'matrix',
|
||||
),
|
||||
'pa' => array(
|
||||
'name' => 'Page',
|
||||
|
@@ -15,7 +15,9 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
'type' => 'number'
|
||||
),
|
||||
't' => array(
|
||||
'name' => 'tags'
|
||||
'type' => 'text',
|
||||
'name' => 'tags',
|
||||
'exampleValue' => 'cosplay',
|
||||
)
|
||||
),
|
||||
0 => array()
|
||||
@@ -44,92 +46,23 @@ class DanbooruBridge extends BridgeAbstract {
|
||||
$item['postid'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
|
||||
$item['timestamp'] = time();
|
||||
$thumbnailUri = $element->find('img', 0)->src;
|
||||
$item['tags'] = $this->getTags($element);
|
||||
$item['categories'] = array_filter(explode(' ', $this->getTags($element)));
|
||||
$item['title'] = $this->getName() . ' | ' . $item['postid'];
|
||||
$item['content'] = '<a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $thumbnailUri
|
||||
. '" /></a><br>Tags: '
|
||||
. $item['tags'];
|
||||
. $this->getTags($element);
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$content = getContents($this->getFullURI());
|
||||
|
||||
$html = Fix_Simple_Html_Dom::str_get_html($content);
|
||||
$html = getSimpleHTMLDOMCached($this->getFullURI());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -33,7 +33,8 @@ class DarkReadingBridge extends FeedExpander {
|
||||
'Insider Threats' => '663_Insider%20Threats',
|
||||
'Vulnerability Management' => '664_Vulnerability%20Management',
|
||||
)
|
||||
)
|
||||
),
|
||||
'limit' => self::LIMIT,
|
||||
));
|
||||
|
||||
public function collectData(){
|
||||
@@ -48,7 +49,8 @@ class DarkReadingBridge extends FeedExpander {
|
||||
if ($feed_id != '000') {
|
||||
$feed_url .= '?f_n=' . $feed_id . '&f_ln=' . $feed_name;
|
||||
}
|
||||
$this->collectExpandableDatas($feed_url, 20);
|
||||
$limit = $this->getInput('limit') ?? 10;
|
||||
$this->collectExpandableDatas($feed_url, $limit);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
|
@@ -1,23 +0,0 @@
|
||||
<?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,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@@ -9,7 +9,14 @@ class DavesTrailerPageBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM(static::URI)
|
||||
or returnClientError('No results for this query.');
|
||||
|
||||
foreach ($html->find('tr[!align]') as $tr) {
|
||||
$curr_date = null;
|
||||
foreach ($html->find('tr') as $tr) {
|
||||
// If it's a date row, update the current date
|
||||
if ($tr->align == 'center') {
|
||||
$curr_date = $tr->plaintext;
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = array();
|
||||
|
||||
// title
|
||||
@@ -21,6 +28,9 @@ class DavesTrailerPageBridge extends BridgeAbstract {
|
||||
// uri
|
||||
$item['uri'] = $tr->find('a', 3)->getAttribute('href');
|
||||
|
||||
// date: parsed by FeedItem using strtotime
|
||||
$item['timestamp'] = $curr_date;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -10,6 +10,7 @@ class DealabsBridge extends PepperBridgeAbstract {
|
||||
'q' => array(
|
||||
'name' => 'Mot(s) clé(s)',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'lampe',
|
||||
'required' => true
|
||||
),
|
||||
'hide_expired' => array(
|
||||
@@ -1885,7 +1886,7 @@ class DealabsBridge extends PepperBridgeAbstract {
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'URL discussion à surveiller: https://www.dealabs.com/discussions/titre-1234',
|
||||
'exampleValue' => 'https://www.dealabs.com/discussions/titre-1234',
|
||||
'exampleValue' => 'https://www.dealabs.com/discussions/jeux-steam-gratuits-gleam-woobox-etc-1071415',
|
||||
),
|
||||
|
||||
'only_with_url' => array(
|
||||
@@ -1962,682 +1963,3 @@ class DealabsBridge extends PepperBridgeAbstract {
|
||||
|
||||
|
||||
}
|
||||
|
||||
class PepperBridgeAbstract extends BridgeAbstract {
|
||||
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case $this->i8n('context-keyword'):
|
||||
return $this->collectDataKeywords();
|
||||
break;
|
||||
case $this->i8n('context-group'):
|
||||
return $this->collectDataGroup();
|
||||
break;
|
||||
case $this->i8n('context-talk'):
|
||||
return $this->collectDataTalk();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Deal data from the choosen group in the choosed order
|
||||
*/
|
||||
protected function collectDataGroup()
|
||||
{
|
||||
$url = $this->getGroupURI();
|
||||
$this->collectDeals($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Deal data from the choosen keywords and parameters
|
||||
*/
|
||||
protected function collectDataKeywords()
|
||||
{
|
||||
/* Even if the original website uses POST with the search page, GET works too */
|
||||
$url = $this->getSearchURI();
|
||||
$this->collectDeals($url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Deal data using the given URL
|
||||
*/
|
||||
protected function collectDeals($url){
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$list = $html->find('article[id]');
|
||||
|
||||
// Deal Image Link CSS Selector
|
||||
$selectorImageLink = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-thread-image-link',
|
||||
'imgFrame',
|
||||
'imgFrame--noBorder',
|
||||
'thread-listImgCell',
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Link CSS Selector
|
||||
$selectorLink = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-tt',
|
||||
'thread-link',
|
||||
'linkPlain',
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Hotness CSS Selector
|
||||
$selectorHot = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-vote-box',
|
||||
'vote-box'
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Description CSS Selector
|
||||
$selectorDescription = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'cept-description-container',
|
||||
'overflow--wrap-break'
|
||||
)
|
||||
);
|
||||
|
||||
// Deal Date CSS Selector
|
||||
$selectorDate = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'size--all-s',
|
||||
'flex',
|
||||
'boxAlign-jc--all-fe'
|
||||
)
|
||||
);
|
||||
|
||||
// If there is no results, we don't parse the content because it display some random deals
|
||||
$noresult = $html->find('h3[class=size--all-l size--fromW2-xl size--fromW3-xxl]', 0);
|
||||
if ($noresult != null && strpos($noresult->plaintext, $this->i8n('no-results')) !== false) {
|
||||
$this->items = array();
|
||||
} else {
|
||||
foreach ($list as $deal) {
|
||||
$item = array();
|
||||
$item['uri'] = $this->getDealURI($deal);
|
||||
$item['title'] = $this->GetTitle($deal);
|
||||
$item['author'] = $deal->find('span.thread-username', 0)->plaintext;
|
||||
|
||||
$item['content'] = '<table><tr><td><a href="'
|
||||
. $item['uri']
|
||||
. '"><img src="'
|
||||
. $this->getImage($deal)
|
||||
. '"/></td><td>'
|
||||
. $this->getHTMLTitle($item)
|
||||
. $this->getPrice($deal)
|
||||
. $this->getDiscount($deal)
|
||||
. $this->getShipsFrom($deal)
|
||||
. $this->getShippingCost($deal)
|
||||
. $this->GetSource($deal)
|
||||
. $deal->find('div[class*=' . $selectorDescription . ']', 0)->innertext
|
||||
. '</td><td>'
|
||||
. $deal->find('div[class*=' . $selectorHot . ']', 0)
|
||||
->find('span', 1)->outertext
|
||||
. '</td></table>';
|
||||
$dealDateDiv = $deal->find('div[class*=' . $selectorDate . ']', 0)
|
||||
->find('span[class=hide--toW3]');
|
||||
$itemDate = end($dealDateDiv)->plaintext;
|
||||
// In case of a Local deal, there is no date, but we can use
|
||||
// this case for other reason (like date not in the last field)
|
||||
if ($this->contains($itemDate, $this->i8n('localdeal'))) {
|
||||
$item['timestamp'] = time();
|
||||
} else if ($this->contains($itemDate, $this->i8n('relative-date-indicator'))) {
|
||||
$item['timestamp'] = $this->relativeDateToTimestamp($itemDate);
|
||||
} else {
|
||||
$item['timestamp'] = $this->parseDate($itemDate);
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Talk lastest comments
|
||||
*/
|
||||
protected function collectDataTalk(){
|
||||
$threadURL = $this->getInput('url');
|
||||
$onlyWithUrl = $this->getInput('only_with_url');
|
||||
|
||||
// Get Thread ID from url passed in parameter
|
||||
$threadSearch = preg_match('/-([0-9]{1,20})$/', $threadURL, $matches);
|
||||
|
||||
// Show an error message if we can't find the thread ID in the URL sent by the user
|
||||
if($threadSearch !== 1) {
|
||||
returnClientError($this->i8n('thread-error'));
|
||||
}
|
||||
$threadID = $matches[1];
|
||||
|
||||
$url = $this->i8n('bridge-uri') . 'graphql';
|
||||
|
||||
// Get Cookies header to do the query
|
||||
$cookies = $this->getCookies($url);
|
||||
|
||||
// GraphQL String
|
||||
// This was extracted from https://www.dealabs.com/assets/js/modern/common_211b99.js
|
||||
// This string was extracted during a Website visit, and minified using this neat tool :
|
||||
// https://codepen.io/dangodev/pen/Baoqmoy
|
||||
$graphqlString = <<<'HEREDOC'
|
||||
query comments($filter:CommentFilter!,$limit:Int,$page:Int){comments(filter:$filter,limit:$limit,page:$page){
|
||||
items{...commentFields}pagination{...paginationFields}}}fragment commentFields on Comment{commentId threadId url
|
||||
preparedHtmlContent user{...userMediumAvatarFields...userNameFields...userPersonaFields bestBadge{...badgeFields}}
|
||||
reactionCounts{type count}deletable currentUserReaction{type}reported reportable source status createdAt updatedAt
|
||||
ignored popular deletedBy{username}notes{content createdAt user{username}}lastEdit{reason timeAgo userId}}fragment
|
||||
userMediumAvatarFields on User{userId isDeletedOrPendingDeletion imageUrls(slot:"default",variations:
|
||||
["user_small_avatar"])}fragment userNameFields on User{userId username isUserProfileHidden isDeletedOrPendingDeletion}
|
||||
fragment userPersonaFields on User{persona{type text}}fragment badgeFields on Badge{badgeId level{...badgeLevelFields}}
|
||||
fragment badgeLevelFields on BadgeLevel{key name description}fragment paginationFields on Pagination{count current last
|
||||
next previous size order}
|
||||
HEREDOC;
|
||||
|
||||
// Construct the JSON object to send to the Website
|
||||
$queryArray = array (
|
||||
'query' => $graphqlString,
|
||||
'variables' => array (
|
||||
'filter' => array (
|
||||
'threadId' => array (
|
||||
'eq' => $threadID,
|
||||
),
|
||||
'order' => array (
|
||||
'direction' => 'Descending',
|
||||
),
|
||||
|
||||
),
|
||||
'page' => 1,
|
||||
),
|
||||
);
|
||||
$queryJSON = json_encode($queryArray);
|
||||
|
||||
// HTTP headers
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json, text/plain, */*',
|
||||
'X-Pepper-Txn: threads.show',
|
||||
'X-Request-Type: application/vnd.pepper.v1+json',
|
||||
'X-Requested-With: XMLHttpRequest',
|
||||
$cookies,
|
||||
);
|
||||
// CURL Options
|
||||
$opts = array(
|
||||
CURLOPT_POST => 1,
|
||||
CURLOPT_POSTFIELDS => $queryJSON
|
||||
);
|
||||
$json = getContents($url, $header, $opts);
|
||||
$objects = json_decode($json);
|
||||
foreach($objects->data->comments->items as $comment) {
|
||||
$item = array();
|
||||
$item['uri'] = $comment->url;
|
||||
$item['title'] = $comment->user->username . ' - ' . $comment->createdAt;
|
||||
$item['author'] = $comment->user->username;
|
||||
$item['content'] = $comment->preparedHtmlContent;
|
||||
$item['uid'] = $comment->commentId;
|
||||
// Timestamp handling needs a new parsing function
|
||||
if($onlyWithUrl == true) {
|
||||
// Count Links and Quote Links
|
||||
$content = str_get_html($item['content']);
|
||||
$countLinks = count($content->find('a[href]'));
|
||||
$countQuoteLinks = count($content->find('a[href][class=userHtml-quote-source]'));
|
||||
// Only add element if there are Links ans more links tant Quote links
|
||||
if($countLinks > 0 && $countLinks > $countQuoteLinks) {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
} else {
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the cookies obtained from the URL
|
||||
* @return array the array containing the cookies set by the URL
|
||||
*/
|
||||
private function getCookies($url)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
|
||||
// get headers too with this line
|
||||
curl_setopt($ch, CURLOPT_HEADER, 1);
|
||||
$result = curl_exec($ch);
|
||||
// get cookie
|
||||
// multi-cookie variant contributed by @Combuster in comments
|
||||
preg_match_all('/^Set-Cookie:\s*([^;]*)/mi', $result, $matches);
|
||||
$cookies = array();
|
||||
foreach($matches[1] as $item) {
|
||||
parse_str($item, $cookie);
|
||||
$cookies = array_merge($cookies, $cookie);
|
||||
}
|
||||
$header = 'Cookie: ';
|
||||
foreach($cookies as $name => $content) {
|
||||
$header .= $name . '=' . $content . '; ';
|
||||
}
|
||||
return $header;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the string $str contains any of the string of the array $arr
|
||||
* @return boolean true if the string matched anything otherwise false
|
||||
*/
|
||||
private function contains($str, array $arr)
|
||||
{
|
||||
foreach ($arr as $a) {
|
||||
if (stripos($str, $a) !== false) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Price from a Deal if it exists
|
||||
* @return string String of the deal price
|
||||
*/
|
||||
private function getPrice($deal)
|
||||
{
|
||||
if ($deal->find(
|
||||
'span[class*=thread-price]', 0) != null) {
|
||||
return '<div>' . $this->i8n('price') . ' : '
|
||||
. $deal->find(
|
||||
'span[class*=thread-price]', 0
|
||||
)->plaintext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Title from a Deal if it exists
|
||||
* @return string String of the deal title
|
||||
*/
|
||||
private function getTitle($deal)
|
||||
{
|
||||
|
||||
$titleRoot = $deal->find('div[class*=threadGrid-title]', 0);
|
||||
$titleA = $titleRoot->find('a[class*=thread-link]', 0);
|
||||
$titleFirstChild = $titleRoot->first_child();
|
||||
if($titleA !== null) {
|
||||
$title = $titleA->plaintext;
|
||||
} else {
|
||||
// Inb ssome case, expired deals have a different format
|
||||
$title = $titleRoot->find('span', 0)->plaintext;
|
||||
}
|
||||
|
||||
return $title;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Title from a Talk if it exists
|
||||
* @return string String of the Talk title
|
||||
*/
|
||||
private function getTalkTitle()
|
||||
{
|
||||
$html = getSimpleHTMLDOMCached($this->getInput('url'));
|
||||
$title = $html->find('h1[class=thread-title]', 0)->plaintext;
|
||||
return $title;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the HTML Title code from an item
|
||||
* @return string String of the deal title
|
||||
*/
|
||||
private function getHTMLTitle($item)
|
||||
{
|
||||
if($item['uri'] == '') {
|
||||
$html = '<h2>' . $item['title'] . '</h2>';
|
||||
} else {
|
||||
$html = '<h2><a href="' . $item['uri'] . '">'
|
||||
. $item['title'] . '</a></h2>';
|
||||
}
|
||||
|
||||
return $html;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URI from a Deal if it exists
|
||||
* @return string String of the deal URI
|
||||
*/
|
||||
private function getDealURI($deal)
|
||||
{
|
||||
|
||||
$uriA = $deal->find('div[class*=threadGrid-title]', 0)->find('a[class*=thread-link]', 0);
|
||||
if($uriA === null) {
|
||||
$uri = '';
|
||||
} else {
|
||||
$uri = $uriA->href;
|
||||
}
|
||||
|
||||
return $uri;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Shipping costs from a Deal if it exists
|
||||
* @return string String of the deal shipping Cost
|
||||
*/
|
||||
private function getShippingCost($deal)
|
||||
{
|
||||
if ($deal->find('span[class*=cept-shipping-price]', 0) != null) {
|
||||
if ($deal->find('span[class*=cept-shipping-price]', 0)->children(0) != null) {
|
||||
return '<div>' . $this->i8n('shipping') . ' : '
|
||||
. $deal->find('span[class*=cept-shipping-price]', 0)->children(0)->innertext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '<div>' . $this->i8n('shipping') . ' : '
|
||||
. $deal->find('span[class*=cept-shipping-price]', 0)->innertext
|
||||
. '</div>';
|
||||
}
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the source of a Deal if it exists
|
||||
* @return string String of the deal source
|
||||
*/
|
||||
private function GetSource($deal)
|
||||
{
|
||||
if ($deal->find('a[class=text--color-greyShade]', 0) != null) {
|
||||
return '<div>' . $this->i8n('origin') . ' : '
|
||||
. $deal->find('a[class=text--color-greyShade]', 0)->outertext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the original Price and discout from a Deal if it exists
|
||||
* @return string String of the deal original price and discount
|
||||
*/
|
||||
private function getDiscount($deal)
|
||||
{
|
||||
if ($deal->find('span[class*=mute--text text--lineThrough]', 0) != null) {
|
||||
$discountHtml = $deal->find('span[class=space--ml-1 size--all-l size--fromW3-xl]', 0);
|
||||
if ($discountHtml != null) {
|
||||
$discount = $discountHtml->plaintext;
|
||||
} else {
|
||||
$discount = '';
|
||||
}
|
||||
return '<div>' . $this->i8n('discount') . ' : <span style="text-decoration: line-through;">'
|
||||
. $deal->find(
|
||||
'span[class*=mute--text text--lineThrough]', 0
|
||||
)->plaintext
|
||||
. '</span> '
|
||||
. $discount
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Picture URL from a Deal if it exists
|
||||
* @return string String of the deal Picture URL
|
||||
*/
|
||||
private function getImage($deal)
|
||||
{
|
||||
$selectorLazy = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'thread-image',
|
||||
'width--all-auto',
|
||||
'height--all-auto',
|
||||
'imgFrame-img',
|
||||
'cept-thread-img',
|
||||
'img--dummy',
|
||||
'js-lazy-img'
|
||||
)
|
||||
);
|
||||
|
||||
$selectorPlain = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'thread-image',
|
||||
'width--all-auto',
|
||||
'height--all-auto',
|
||||
'imgFrame-img',
|
||||
'cept-thread-img'
|
||||
)
|
||||
);
|
||||
if ($deal->find('img[class=' . $selectorLazy . ']', 0) != null) {
|
||||
return json_decode(
|
||||
html_entity_decode(
|
||||
$deal->find('img[class=' . $selectorLazy . ']', 0)
|
||||
->getAttribute('data-lazy-img')))->{'src'};
|
||||
} else {
|
||||
return $deal->find('img[class*=' . $selectorPlain . ']', 0 )->src;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the originating country from a Deal if it exists
|
||||
* @return string String of the deal originating country
|
||||
*/
|
||||
private function getShipsFrom($deal)
|
||||
{
|
||||
$selector = implode(
|
||||
' ', /* Notice this is a space! */
|
||||
array(
|
||||
'meta-ribbon',
|
||||
'overflow--wrap-off',
|
||||
'space--l-3',
|
||||
'text--color-greyShade'
|
||||
)
|
||||
);
|
||||
if ($deal->find('span[class=' . $selector . ']', 0) != null) {
|
||||
return '<div>'
|
||||
. $deal->find('span[class=' . $selector . ']', 0)->children(2)->plaintext
|
||||
. '</div>';
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a local date into a timestamp
|
||||
* @return int timestamp of the input date
|
||||
*/
|
||||
private function parseDate($string)
|
||||
{
|
||||
$month_local = $this->i8n('local-months');
|
||||
$month_en = array(
|
||||
'January',
|
||||
'February',
|
||||
'March',
|
||||
'April',
|
||||
'May',
|
||||
'June',
|
||||
'July',
|
||||
'August',
|
||||
'September',
|
||||
'October',
|
||||
'November',
|
||||
'December'
|
||||
);
|
||||
|
||||
// A date can be prfixed with some words, we remove theme
|
||||
$string = $this->removeDatePrefixes($string);
|
||||
// We translate the local months name in the english one
|
||||
$date_str = trim(str_replace($month_local, $month_en, $string));
|
||||
|
||||
// If the date does not contain any year, we add the current year
|
||||
if (!preg_match('/[0-9]{4}/', $string)) {
|
||||
$date_str .= ' ' . date('Y');
|
||||
}
|
||||
|
||||
// Add the Hour and minutes
|
||||
$date_str .= ' 00:00';
|
||||
$date = DateTime::createFromFormat('j F Y H:i', $date_str);
|
||||
// In some case, the date is not recognized : as a workaround the actual date is taken
|
||||
if($date === false) {
|
||||
$date = new DateTime();
|
||||
}
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the prefix of a date if it has one
|
||||
* @return the date without prefiux
|
||||
*/
|
||||
private function removeDatePrefixes($string)
|
||||
{
|
||||
$string = str_replace($this->i8n('date-prefixes'), array(), $string);
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the suffix of a relative date if it has one
|
||||
* @return the relative date without suffixes
|
||||
*/
|
||||
private function removeRelativeDateSuffixes($string)
|
||||
{
|
||||
if (count($this->i8n('relative-date-ignore-suffix')) > 0) {
|
||||
$string = preg_replace($this->i8n('relative-date-ignore-suffix'), '', $string);
|
||||
}
|
||||
return $string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a relative local date into a timestamp
|
||||
* @return int timestamp of the input date
|
||||
*/
|
||||
private function relativeDateToTimestamp($str) {
|
||||
$date = new DateTime();
|
||||
|
||||
// In case of update date, replace it by the regular relative date first word
|
||||
$str = str_replace($this->i8n('relative-date-alt-prefixes'), $this->i8n('local-time-relative')[0], $str);
|
||||
|
||||
$str = $this->removeRelativeDateSuffixes($str);
|
||||
|
||||
$search = $this->i8n('local-time-relative');
|
||||
|
||||
$replace = array(
|
||||
'-',
|
||||
'minute',
|
||||
'hour',
|
||||
'day',
|
||||
'month',
|
||||
'year',
|
||||
''
|
||||
);
|
||||
|
||||
$date->modify(str_replace($search, $replace, $str));
|
||||
return $date->getTimestamp();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RSS Feed title according to the parameters
|
||||
* @return string the RSS feed Tiyle
|
||||
*/
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case $this->i8n('context-keyword'):
|
||||
return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-keyword') . ' : ' . $this->getInput('q');
|
||||
break;
|
||||
case $this->i8n('context-group'):
|
||||
$values = $this->getParameters()[$this->i8n('context-group')]['group']['values'];
|
||||
$group = array_search($this->getInput('group'), $values);
|
||||
return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-group') . ' : ' . $group;
|
||||
break;
|
||||
case $this->i8n('context-talk'):
|
||||
return $this->i8n('bridge-name') . ' - ' . $this->i8n('title-talk') . ' : ' . $this->getTalkTitle();
|
||||
break;
|
||||
default: // Return default value
|
||||
return static::NAME;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RSS Feed URI according to the parameters
|
||||
* @return string the RSS feed Title
|
||||
*/
|
||||
public function getURI(){
|
||||
switch($this->queriedContext) {
|
||||
case $this->i8n('context-keyword'):
|
||||
return $this->getSearchURI();
|
||||
break;
|
||||
case $this->i8n('context-group'):
|
||||
return $this->getGroupURI();
|
||||
break;
|
||||
case $this->i8n('context-talk'):
|
||||
return $this->getTalkURI();
|
||||
break;
|
||||
default: // Return default value
|
||||
return static::URI;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RSS Feed URI for a keyword Feed
|
||||
* @return string the RSS feed URI
|
||||
*/
|
||||
private function getSearchURI(){
|
||||
$q = $this->getInput('q');
|
||||
$hide_expired = $this->getInput('hide_expired');
|
||||
$hide_local = $this->getInput('hide_local');
|
||||
$priceFrom = $this->getInput('priceFrom');
|
||||
$priceTo = $this->getInput('priceTo');
|
||||
$url = $this->i8n('bridge-uri')
|
||||
. 'search/advanced?q='
|
||||
. urlencode($q)
|
||||
. '&hide_expired=' . $hide_expired
|
||||
. '&hide_local=' . $hide_local
|
||||
. '&priceFrom=' . $priceFrom
|
||||
. '&priceTo=' . $priceTo
|
||||
/* Some default parameters
|
||||
* search_fields : Search in Titres & Descriptions & Codes
|
||||
* sort_by : Sort the search by new deals
|
||||
* time_frame : Search will not be on a limited timeframe
|
||||
*/
|
||||
. '&search_fields[]=1&search_fields[]=2&search_fields[]=3&sort_by=new&time_frame=0';
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RSS Feed URI for a group Feed
|
||||
* @return string the RSS feed URI
|
||||
*/
|
||||
private function getGroupURI(){
|
||||
$group = $this->getInput('group');
|
||||
$order = $this->getInput('order');
|
||||
|
||||
$url = $this->i8n('bridge-uri')
|
||||
. $this->i8n('uri-group') . $group . $order;
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the RSS Feed URI for a Talk Feed
|
||||
* @return string the RSS feed URI
|
||||
*/
|
||||
private function getTalkURI(){
|
||||
$url = $this->getInput('url');
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is some "localisation" function that returns the needed content using
|
||||
* the "$lang" class variable in the local class
|
||||
* @return various the local content needed
|
||||
*/
|
||||
protected function i8n($key)
|
||||
{
|
||||
if (array_key_exists($key, $this->lang)) {
|
||||
return $this->lang[$key];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -24,7 +24,8 @@ class DerpibooruBridge extends BridgeAbstract {
|
||||
),
|
||||
'q' => array(
|
||||
'name' => 'Query',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'dog',
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@@ -16,7 +16,7 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'title' => 'Select your language',
|
||||
'defaultValue' => 'Corporate',
|
||||
'defaultValue' => 'https://www.desouttertools.com/about-desoutter/news-events',
|
||||
'values' => array(
|
||||
'Corporate'
|
||||
=> 'https://www.desouttertools.com/about-desoutter/news-events',
|
||||
@@ -120,6 +120,7 @@ class DesoutterBridge extends BridgeAbstract {
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 3,
|
||||
'title' => "Maximum number of items to return in the feed.\n0 = unlimited"
|
||||
)
|
||||
|
@@ -1,47 +1,407 @@
|
||||
<?php
|
||||
class DeveloppezDotComBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'polopollo';
|
||||
class DeveloppezDotComBridge extends FeedExpander
|
||||
{
|
||||
|
||||
const MAINTAINER = 'Binnette';
|
||||
const NAME = 'Developpez.com Actus (FR)';
|
||||
const URI = 'https://www.developpez.com/';
|
||||
const DOMAIN = '.developpez.com/';
|
||||
const RSS_URL = 'index/rss';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
const DESCRIPTION = 'Returns the 15 newest posts from DeveloppezDotCom (full text).';
|
||||
const DESCRIPTION = 'Returns complete posts from developpez.com';
|
||||
// Encodings used by Developpez.com in their articles body
|
||||
const ENCONDINGS = array('Windows-1252', 'UTF-8');
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'limit' => array(
|
||||
'name' => 'Max items',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 5,
|
||||
),
|
||||
// list of the differents RSS availables
|
||||
'domain' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Domaine',
|
||||
'title' => 'Chosissez un sous-domaine',
|
||||
'values' => array(
|
||||
'= Domaine principal =' => 'www',
|
||||
'4d' => '4d',
|
||||
'abbyy' => 'abbyy',
|
||||
'access' => 'access',
|
||||
'agile' => 'agile',
|
||||
'ajax' => 'ajax',
|
||||
'algo' => 'algo',
|
||||
'alm' => 'alm',
|
||||
'android' => 'android',
|
||||
'apache' => 'apache',
|
||||
'applications' => 'applications',
|
||||
'arduino' => 'arduino',
|
||||
'asm' => 'asm',
|
||||
'asp' => 'asp',
|
||||
'aspose' => 'aspose',
|
||||
'bacasable' => 'bacasable',
|
||||
'big-data' => 'big-data',
|
||||
'bpm' => 'bpm',
|
||||
'bsd' => 'bsd',
|
||||
'business-intelligence' => 'business-intelligence',
|
||||
'c' => 'c',
|
||||
'cloud-computing' => 'cloud-computing',
|
||||
'club' => 'club',
|
||||
'cms' => 'cms',
|
||||
'cpp' => 'cpp',
|
||||
'crm' => 'crm',
|
||||
'css' => 'css',
|
||||
'd' => 'd',
|
||||
'dart' => 'dart',
|
||||
'data-science' => 'data-science',
|
||||
'db2' => 'db2',
|
||||
'delphi' => 'delphi',
|
||||
'dotnet' => 'dotnet',
|
||||
'droit' => 'droit',
|
||||
'eclipse' => 'eclipse',
|
||||
'edi' => 'edi',
|
||||
'embarque' => 'embarque',
|
||||
'emploi' => 'emploi',
|
||||
'etudes' => 'etudes',
|
||||
'excel' => 'excel',
|
||||
'firebird' => 'firebird',
|
||||
'flash' => 'flash',
|
||||
'go' => 'go',
|
||||
'green-it' => 'green-it',
|
||||
'gtk' => 'gtk',
|
||||
'hardware' => 'hardware',
|
||||
'hpc' => 'hpc',
|
||||
'humour' => 'humour',
|
||||
'ibmcloud' => 'ibmcloud',
|
||||
'intelligence-artificielle' => 'intelligence-artificielle',
|
||||
'interbase' => 'interbase',
|
||||
'ios' => 'ios',
|
||||
'java' => 'java',
|
||||
'javascript' => 'javascript',
|
||||
'javaweb' => 'javaweb',
|
||||
'jetbrains' => 'jetbrains',
|
||||
'jeux' => 'jeux',
|
||||
'kotlin' => 'kotlin',
|
||||
'labview' => 'labview',
|
||||
'laravel' => 'laravel',
|
||||
'latex' => 'latex',
|
||||
'lazarus' => 'lazarus',
|
||||
'linux' => 'linux',
|
||||
'mac' => 'mac',
|
||||
'matlab' => 'matlab',
|
||||
'megaoffice' => 'megaoffice',
|
||||
'merise' => 'merise',
|
||||
'microsoft' => 'microsoft',
|
||||
'mobiles' => 'mobiles',
|
||||
'mongodb' => 'mongodb',
|
||||
'mysql' => 'mysql',
|
||||
'netbeans' => 'netbeans',
|
||||
'nodejs' => 'nodejs',
|
||||
'nosql' => 'nosql',
|
||||
'objective-c' => 'objective-c',
|
||||
'office' => 'office',
|
||||
'open-source' => 'open-source',
|
||||
'openoffice-libreoffice' => 'openoffice-libreoffice',
|
||||
'oracle' => 'oracle',
|
||||
'outlook' => 'outlook',
|
||||
'pascal' => 'pascal',
|
||||
'perl' => 'perl',
|
||||
'php' => 'php',
|
||||
'portail-emploi' => 'portail-emploi',
|
||||
'portail-projets' => 'portail-projets',
|
||||
'postgresql' => 'postgresql',
|
||||
'powerpoint' => 'powerpoint',
|
||||
'preprod-emploi' => 'preprod-emploi',
|
||||
'programmation' => 'programmation',
|
||||
'project' => 'project',
|
||||
'purebasic' => 'purebasic',
|
||||
'pyqt' => 'pyqt',
|
||||
'python' => 'python',
|
||||
'qt-creator' => 'qt-creator',
|
||||
'qt' => 'qt',
|
||||
'r' => 'r',
|
||||
'raspberry-pi' => 'raspberry-pi',
|
||||
'reseau' => 'reseau',
|
||||
'ruby' => 'ruby',
|
||||
'rust' => 'rust',
|
||||
'sap' => 'sap',
|
||||
'sas' => 'sas',
|
||||
'scilab' => 'scilab',
|
||||
'securite' => 'securite',
|
||||
'sgbd' => 'sgbd',
|
||||
'sharepoint' => 'sharepoint',
|
||||
'solutions-entreprise' => 'solutions-entreprise',
|
||||
'spring' => 'spring',
|
||||
'sqlserver' => 'sqlserver',
|
||||
'stages' => 'stages',
|
||||
'supervision' => 'supervision',
|
||||
'swift' => 'swift',
|
||||
'sybase' => 'sybase',
|
||||
'symfony' => 'symfony',
|
||||
'systeme' => 'systeme',
|
||||
'talend' => 'talend',
|
||||
'typescript' => 'typescript',
|
||||
'uml' => 'uml',
|
||||
'unix' => 'unix',
|
||||
'vb' => 'vb',
|
||||
'vba' => 'vba',
|
||||
'virtualisation' => 'virtualisation',
|
||||
'visualstudio' => 'visualstudio',
|
||||
'web-semantique' => 'web-semantique',
|
||||
'web' => 'web',
|
||||
'webmarketing' => 'webmarketing',
|
||||
'wind' => 'wind',
|
||||
'windows-azure' => 'windows-azure',
|
||||
'windows' => 'windows',
|
||||
'windowsphone' => 'windowsphone',
|
||||
'word' => 'word',
|
||||
'xhtml' => 'xhtml',
|
||||
'xml' => 'xml',
|
||||
'zend-framework' => 'zend-framework'
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas(self::URI . 'index/rss', 15);
|
||||
/**
|
||||
* Return the RSS url for selected domain
|
||||
*/
|
||||
private function getRssUrl()
|
||||
{
|
||||
$domain = $this->getInput('domain');
|
||||
if (!empty($domain)) {
|
||||
return 'https://' . $domain . self::DOMAIN . self::RSS_URL;
|
||||
}
|
||||
|
||||
return self::URI . self::RSS_URL;
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem){
|
||||
/**
|
||||
* Grabs the RSS item from Developpez.com
|
||||
*/
|
||||
public function collectData()
|
||||
{
|
||||
$url = $this->getRssUrl();
|
||||
$this->collectExpandableDatas($url, 20);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the content of every RSS item. And will try to get the full article
|
||||
* pointed by the item URL intead of the default abstract.
|
||||
*/
|
||||
protected function parseItem($newsItem)
|
||||
{
|
||||
if (count($this->items) >= $this->getInput('limit')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This function parse each entry in the RSS with the default parse
|
||||
$item = parent::parseItem($newsItem);
|
||||
$item['content'] = $this->extractContent($item['uri']);
|
||||
|
||||
// There is a bug in Developpez RSS, coma are writtent as '~?' in the
|
||||
// title, so I have to fix it manually
|
||||
$item['title'] = $this->fixComaInTitle($item['title']);
|
||||
|
||||
// We get the content of the full article behind the RSS item URL
|
||||
$articleHTMLContent = getSimpleHTMLDOMCached($item['uri']);
|
||||
|
||||
// Here we call our custom parser
|
||||
$fullText = $this->extractFullText($articleHTMLContent);
|
||||
if (!is_null($fullText)) {
|
||||
// if we manage to parse the page behind the url of the RSS item
|
||||
// then we set it as the new content. Otherwise we keep the default
|
||||
// content to avoid RSS Bridge to return an empty item
|
||||
$item['content'] = $fullText;
|
||||
}
|
||||
|
||||
// Now we will attach video url in item
|
||||
$videosUrl = $this->getAllVideoUrl($articleHTMLContent);
|
||||
if (!empty($videosUrl)) {
|
||||
$item['enclosures'] = array_merge($item['enclosures'], $videosUrl);
|
||||
}
|
||||
|
||||
// Now we can look for the blog writer/creator
|
||||
$author = $articleHTMLContent->find('[itemprop="creator"]', 0);
|
||||
if (!empty($author)) {
|
||||
$item['author'] = $author->outertext;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
// F***ing quotes from Microsoft Word badly encoded, here was the trick:
|
||||
// http://stackoverflow.com/questions/1262038/how-to-replace-microsoft-encoded-quotes-in-php
|
||||
private function convertSmartQuotes($string)
|
||||
/**
|
||||
* Replace '~?' by a proper coma ','
|
||||
*/
|
||||
private function fixComaInTitle($txt)
|
||||
{
|
||||
$search = array(chr(145),
|
||||
chr(146),
|
||||
chr(147),
|
||||
chr(148),
|
||||
chr(151));
|
||||
|
||||
$replace = array(
|
||||
"'",
|
||||
"'",
|
||||
'"',
|
||||
'"',
|
||||
'-'
|
||||
);
|
||||
|
||||
return str_replace($search, $replace, $string);
|
||||
return str_replace('~?', ',', $txt);
|
||||
}
|
||||
|
||||
private function extractContent($url){
|
||||
$articleHTMLContent = getSimpleHTMLDOMCached($url);
|
||||
$text = $this->convertSmartQuotes($articleHTMLContent->find('div.content', 0)->innertext);
|
||||
$text = utf8_encode($text);
|
||||
return trim($text);
|
||||
/**
|
||||
* Return the full article pointed by the url in the RSS item
|
||||
* Since Developpez.com only provides a short abstract of the article, we
|
||||
* use the url to retrieve the complete article and return it as the content
|
||||
*/
|
||||
private function extractFullText($articleHTMLContent)
|
||||
{
|
||||
// All blog entry contains a div with the class 'content'. This div
|
||||
// contains the complete blog article. But the RSS can also return
|
||||
// announcement and not a blog article. So the next if, should take
|
||||
// care of the "non blog" entry
|
||||
$divArticleEntry = $articleHTMLContent->find('div.content', 0);
|
||||
if (is_null($divArticleEntry)) {
|
||||
// Didn't find the div with class content. It is probably not a blog
|
||||
// entry. It is probably just an announcement for an ebook, a PDF,
|
||||
// etc. So we can use the default RSS item content.
|
||||
return null;
|
||||
}
|
||||
|
||||
// The following code is a bit hacky, but I really manage to get the
|
||||
// full content of articles without any encoding issues. What is very
|
||||
// weird and ugly in Developpez.com is the fact the some paragraphs of
|
||||
// the article will be encoded as UTF-8 and some other paragraphs will
|
||||
// be encoded as Windows-1252. So we can NOT decode the full article
|
||||
// with only one encoding. We have to check every paragraph and
|
||||
// determine its encoding
|
||||
|
||||
// This contains all the 'paragraphs' of the article. It includes the
|
||||
// pictures, the text and the links at the bottom of the article
|
||||
$paragraphs = $divArticleEntry->nodes;
|
||||
// This will store the complete decoded content
|
||||
$fullText = '';
|
||||
|
||||
// For each paragraph, we will identify the encoding, then decode it
|
||||
// and finally store the decoded content in $text
|
||||
foreach ($paragraphs as $paragraph) {
|
||||
// We have to recreate a new DOM document from the current node
|
||||
// otherwise the find function will look in the complet article and
|
||||
// not only in the current paragraph. This is an ugly behavior of
|
||||
// the library Simple HTML DOM Parser...
|
||||
$html = str_get_html($paragraph->outertext);
|
||||
$fullText .= $this->decodeParagraph($html);
|
||||
}
|
||||
|
||||
// Finally we return the full 'well' enconded content of the article
|
||||
return $fullText;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private function decodeParagraph($p)
|
||||
{
|
||||
// First we check if this paragraph is a video
|
||||
$videoUrl = $this->getVideoUrl($p);
|
||||
if (!empty($videoUrl)) {
|
||||
// If this is a video, we just return a link to the video
|
||||
// 📺 => 🎞️
|
||||
return '<p>
|
||||
<b>📺 <a href="' . $videoUrl . '">Voir la vidéo</a></b>
|
||||
</p>';
|
||||
}
|
||||
|
||||
// We take outertext to get the complete paragraph not only the text
|
||||
// inside it. That way we still graph block <img> and so on.
|
||||
$pTxt = $p->outertext;
|
||||
// This will store the decoded text if we manage to decode it
|
||||
$decodedTxt = '';
|
||||
|
||||
// This is the only way to properly decode each paragraph. I tried
|
||||
// many stuffs but this is the only working way I found.
|
||||
foreach (self::ENCONDINGS as $enc) {
|
||||
// We check the encoding of the current paragraph
|
||||
if (mb_check_encoding($pTxt, $enc)) {
|
||||
// If the encoding is well recognized, we can convert from
|
||||
// this encoding to UTF-8
|
||||
$decodedTxt = iconv($enc, 'UTF-8', $pTxt);
|
||||
}
|
||||
}
|
||||
|
||||
// We should not trim the strings to avoid the <a> to be glued to the
|
||||
// text like: the software<a href="...">started</a>to...
|
||||
if (!empty($decodedTxt)) {
|
||||
// We manage to decode the text, so we take the decoded version
|
||||
return $this->formatParagraph($decodedTxt);
|
||||
} else {
|
||||
// Otherwise we take the non decoded version and hope it will
|
||||
// be displayed not too ugly in the fulltext content
|
||||
return $this->formatParagraph($pTxt);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return true in $txt is a HTML tag and not plain text
|
||||
*/
|
||||
private function isHtmlTagNotTxt($txt)
|
||||
{
|
||||
$html = str_get_html($txt);
|
||||
return $html && $html->root && count($html->root->children) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Will add a space before paragraph when needed
|
||||
*/
|
||||
private function formatParagraph($txt)
|
||||
{
|
||||
// If the paragraph is an html tag, we add a space before
|
||||
if ($this->isHtmlTagNotTxt($txt)) {
|
||||
// the first element is an html tag and not a text, so we can add a
|
||||
// space before it
|
||||
return ' ' . $txt;
|
||||
}
|
||||
// If the text start with word (not punctation), we had a space
|
||||
$pattern = '/^\w/';
|
||||
if (preg_match($pattern, $txt)) {
|
||||
return ' ' . $txt;
|
||||
}
|
||||
return $txt;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve all video url in the article
|
||||
*/
|
||||
private function getAllVideoUrl($item)
|
||||
{
|
||||
// Array of video url
|
||||
$url = array();
|
||||
|
||||
// Developpez use a div with the class video-container
|
||||
$divsVideo = $item->find('div.video-container');
|
||||
if (empty($divsVideo)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// get the url of the video
|
||||
foreach ($divsVideo as $div) {
|
||||
$html = str_get_html($div->outertext);
|
||||
$url[] = $this->getVideoUrl($html);
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve URL video. We have to check for the src of an iframe
|
||||
* Work for Youtube. Will have to test for other video platform
|
||||
*/
|
||||
private function getVideoUrl($p)
|
||||
{
|
||||
$divVideo = $p->find('div.video-container', 0);
|
||||
if (empty($divVideo)) {
|
||||
return null;
|
||||
}
|
||||
$iframe = $divVideo->find('iframe', 0);
|
||||
if (empty($iframe)) {
|
||||
return null;
|
||||
}
|
||||
$src = trim($iframe->getAttribute('src'));
|
||||
if (empty($src)) {
|
||||
return null;
|
||||
}
|
||||
if (str_starts_with($src, '//')) {
|
||||
$src = 'https:' . $src;
|
||||
}
|
||||
return $src;
|
||||
}
|
||||
}
|
||||
|
@@ -8,6 +8,7 @@ class DiarioDeNoticiasBridge extends BridgeAbstract {
|
||||
'Tag' => array(
|
||||
'n' => array(
|
||||
'name' => 'Tag Name',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rogerio-casanova',
|
||||
)
|
||||
)
|
||||
|
@@ -11,18 +11,26 @@ class DiscogsBridge extends BridgeAbstract {
|
||||
'artistid' => array(
|
||||
'name' => 'Artist ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '28104',
|
||||
'title' => 'Only the ID from an artist page. EG /artist/28104-Aesop-Rock is 28104'
|
||||
)
|
||||
),
|
||||
'Label Releases' => array(
|
||||
'labelid' => array(
|
||||
'name' => 'Label ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '8201',
|
||||
'title' => 'Only the ID from a label page. EG /label/8201-Rhymesayers-Entertainment is 8201'
|
||||
)
|
||||
),
|
||||
'User Wantlist' => array(
|
||||
'username_wantlist' => array(
|
||||
'name' => 'Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'TheBlindMaster',
|
||||
)
|
||||
),
|
||||
'User Folder' => array(
|
||||
|
@@ -23,6 +23,7 @@ class DonnonsBridge extends BridgeAbstract {
|
||||
'p' => array(
|
||||
'name' => 'Nombre de pages à scanner',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 5,
|
||||
'title' => 'Indique le nombre de pages de donnons.org qui seront scannées'
|
||||
)
|
||||
@@ -66,7 +67,9 @@ class DonnonsBridge extends BridgeAbstract {
|
||||
$region = $json['availableAtOrFrom']['address']['addressRegion'];
|
||||
|
||||
// Grab info from HTML
|
||||
$imageSrc = $element->find('img.ima-center', 0)->getAttribute('data-src');
|
||||
$imageSrc = $element->find('img.ima-center', 0)->getAttribute('src');
|
||||
// Use large image instead of small one
|
||||
$imageSrc = str_replace('/xs/', '/lg/', $imageSrc);
|
||||
$image = self::URI . $imageSrc;
|
||||
$author = $element->find('div.avatar-holder', 0)->plaintext;
|
||||
|
||||
|
@@ -1,194 +0,0 @@
|
||||
<?php
|
||||
class DownDetectorBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'teromene';
|
||||
const NAME = 'DownDetector Bridge';
|
||||
const URI = 'https://downdetector.com/';
|
||||
const DESCRIPTION = 'Returns most recent downtimes from DownDetector';
|
||||
const CACHE_TIMEOUT = 300; // 5 min
|
||||
|
||||
const PARAMETERS = array(
|
||||
'All Websites' => array(
|
||||
'country' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Country',
|
||||
'values' => array(
|
||||
'Argentina' => 'https://downdetector.com.ar',
|
||||
'Australia' => 'https://downdetector.com.au',
|
||||
'België' => 'https://allestoringen.be',
|
||||
'Brasil' => 'https://downdetector.com.br',
|
||||
'Canada' => 'https://downdetector.ca',
|
||||
'Chile' => 'https://downdetector.cl',
|
||||
'Colombia' => 'https://downdetector.com.co',
|
||||
'Danmark' => 'https://downdetector.dk',
|
||||
'Deutschland' => 'https://allestörungen.de',
|
||||
'Ecuador' => 'https://downdetector.ec',
|
||||
'España' => 'https://downdetector.es',
|
||||
'France' => 'https://downdetector.fr',
|
||||
'Hong Kong' => 'https://downdetector.hk',
|
||||
'Hrvatska' => 'https://downdetector.hr',
|
||||
'India' => 'https://downdetector.in',
|
||||
'Indonesia' => 'https://downdetector.id',
|
||||
'Ireland' => 'https://downdetector.ie',
|
||||
'Italia' => 'https://downdetector.it',
|
||||
'Magyarország' => 'https://downdetector.hu',
|
||||
'Malaysia' => 'https://downdetector.my',
|
||||
'México' => 'https://downdetector.mx',
|
||||
'Nederland' => 'https://allestoringen.nl',
|
||||
'New Zealand' => 'https://downdetector.co.nz',
|
||||
'Norge' => 'https://downdetector.no',
|
||||
'Pakistan' => 'https://downdetector.pk',
|
||||
'Perú' => 'https://downdetector.pe',
|
||||
'Pilipinas' => 'https://downdetector.ph',
|
||||
'Polska' => 'https://downdetector.pl',
|
||||
'Portugal' => 'https://downdetector.pt',
|
||||
'România' => 'https://downdetector.ro',
|
||||
'Schweiz' => 'https://allestörungen.ch',
|
||||
'Singapore' => 'https://downdetector.sg',
|
||||
'Slovensko' => 'https://downdetector.sk',
|
||||
'South Africa' => 'https://downdetector.co.za',
|
||||
'Suomi' => 'https://downdetector.fi',
|
||||
'Sverige' => 'https://downdetector.se',
|
||||
'Türkiye' => 'https://downdetector.web.tr',
|
||||
'UAE' => 'https://downdetector.ae',
|
||||
'UK' => 'https://downdetector.co.uk',
|
||||
'United States' => 'https://downdetector.com',
|
||||
'Österreich' => 'https://allestörungen.at',
|
||||
'Česko' => 'https://downdetector.cz',
|
||||
'Ελλάς' => 'https://downdetector.gr',
|
||||
'Россия' => 'https://downdetector.ru',
|
||||
'日本' => 'https://downdetector.jp'
|
||||
)
|
||||
)
|
||||
),
|
||||
'Specific Website' => array(
|
||||
'page' => array(
|
||||
'type' => 'text',
|
||||
'name' => 'Status page',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://downdetector.com/status/rainbow-six',
|
||||
'title' => 'URL of a DownDetector status page e.g: https://downdetector.com/status/rainbow-six/',
|
||||
)
|
||||
),
|
||||
);
|
||||
|
||||
private $hostname = '';
|
||||
private $statusPageId = '';
|
||||
private $feedname = '';
|
||||
|
||||
private $statusUrlRegex = '/\/([a-zA-z0-9ö.]+)\/(?:statu(?:s|t)|problemas?|nu-merge
|
||||
|(?:feil-)?problem(y|i)?(?:-storningar)?(?:-fejl)?|stoerung|durum|storing|fora-do-ar|ne-rabotaet
|
||||
|masalah|shougai|ei-toimi)\/([a-zA-Z0-9-]+)/';
|
||||
|
||||
public function collectData(){
|
||||
if ($this->queriedContext == 'Specific Website') {
|
||||
preg_match($this->statusUrlRegex, $this->getInput('page'), $match)
|
||||
or returnClientError('Given URL does not seem to at a DownDetector status page!');
|
||||
|
||||
$this->hostname = $match[1];
|
||||
$this->statusPageId = $match[3];
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/archive/')
|
||||
or returnClientError('Could not request website!.');
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
$this->feedname = $html->find('li.breadcrumb-item.active', 0)->plaintext;
|
||||
}
|
||||
|
||||
$table = $html->find('table.table-striped', 0);
|
||||
|
||||
if ($table) {
|
||||
foreach ($table->find('tr') as $event) {
|
||||
$td = $event->find('td', 0);
|
||||
|
||||
if (is_null($td)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['uri'] = $event->find('td', 0)->find('a', 0)->href;
|
||||
$item['title'] = $event->find('td', 0)->find('a', 0)->plaintext .
|
||||
'(' . trim($event->find('td', 1)->plaintext) . ' ' . trim($event->find('td', 2)->plaintext) . ')';
|
||||
$item['content'] = 'User reports indicate problems at' . $event->find('td', 0)->find('a', 0)->plaintext .
|
||||
' since ' . $event->find('td', 2)->plaintext;
|
||||
$item['timestamp'] = $this->formatDate(
|
||||
trim($event->find('td', 1)->plaintext),
|
||||
trim($event->find('td', 2)->plaintext)
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
if($this->getInput('country')) {
|
||||
return $this->getInput('country');
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
return 'https://' . $this->hostname . '/status/' . $this->statusPageId;
|
||||
}
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if($this->getInput('country')) {
|
||||
$country = $this->getCountry($this->getInput('country'));
|
||||
return $country . ' - DownDetector';
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
$country = $this->getCountry($this->hostname);
|
||||
return $this->feedname . ' - ' . $country . ' - DownDetector';
|
||||
}
|
||||
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
private function formatDate($date, $time) {
|
||||
switch($this->getCountry()) {
|
||||
case 'Australia':
|
||||
case 'UK':
|
||||
$date = DateTime::createFromFormat('d/m/Y', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
case 'Brasil':
|
||||
case 'Chile':
|
||||
case 'Colombia':
|
||||
case 'Ecuador':
|
||||
case 'España':
|
||||
case 'Italia':
|
||||
case 'Perú':
|
||||
case 'Portugal':
|
||||
$date = DateTime::createFromFormat('d/m/Y', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
case 'Magyarország':
|
||||
$date = DateTime::createFromFormat('Y.m.d.', $date);
|
||||
return $date->format('Y-m-d') . $time;
|
||||
default:
|
||||
return $date . $time;
|
||||
}
|
||||
}
|
||||
|
||||
private function getCountry() {
|
||||
if($this->getInput('country')) {
|
||||
$input = $this->getInput('country');
|
||||
}
|
||||
|
||||
if ($this->getInput('page')) {
|
||||
if (empty($this->hostname)) {
|
||||
return 'N/A';
|
||||
}
|
||||
|
||||
$input = 'https://' . $this->hostname;
|
||||
}
|
||||
|
||||
$parameters = $this->getParameters();
|
||||
$countryValues = array_flip($parameters['All Websites']['country']['values']);
|
||||
$country = $countryValues[$input];
|
||||
|
||||
return $country;
|
||||
}
|
||||
}
|
@@ -13,6 +13,7 @@ class DuckDuckGoBridge extends BridgeAbstract {
|
||||
const PARAMETERS = array( array(
|
||||
'u' => array(
|
||||
'name' => 'keyword',
|
||||
'exampleValue' => 'duck',
|
||||
'required' => true
|
||||
),
|
||||
'sort' => array(
|
||||
|
@@ -1,159 +0,0 @@
|
||||
<?php
|
||||
class ETTVBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'GregThib';
|
||||
const NAME = 'ETTV';
|
||||
const URI = 'https://www.ettv.tv/';
|
||||
const DESCRIPTION = 'Returns list of 20 latest torrents for a specific search.';
|
||||
const CACHE_TIMEOUT = 14400; // 4 hours
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'query' => array(
|
||||
'name' => 'Keywords',
|
||||
'required' => true
|
||||
),
|
||||
'cat' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Category',
|
||||
'values' => array(
|
||||
'(ALL TYPES)' => '0',
|
||||
'Anime: Movies' => '73',
|
||||
'Anime: Dubbed/Subbed' => '74',
|
||||
'Anime: Others' => '75',
|
||||
'Books: Ebooks' => '53',
|
||||
'Books: Magazines' => '54',
|
||||
'Books: Comics' => '55',
|
||||
'Books: Audio' => '56',
|
||||
'Books: Others' => '68',
|
||||
'Games: Windows' => '57',
|
||||
'Games: Android' => '58',
|
||||
'Games: Others' => '71',
|
||||
'Movies: HD 1080p' => '1',
|
||||
'Movies: HD 720p' => '2',
|
||||
'Movies: UltraHD/4K' => '3',
|
||||
'Movies: XviD' => '42',
|
||||
'Movies: X264/H264' => '47',
|
||||
'Movies: 3D' => '49',
|
||||
'Movies: Dubs/Dual Audio' => '51',
|
||||
'Movies: CAM/TS' => '65',
|
||||
'Movies: BluRay Disc/Remux' => '66',
|
||||
'Movies: DVDR' => '67',
|
||||
'Movies: HEVC/x265' => '76',
|
||||
'Music: MP3' => '59',
|
||||
'Music: FLAC' => '60',
|
||||
'Music: Music Videos' => '61',
|
||||
'Music: Others' => '69',
|
||||
'Software: Windows' => '62',
|
||||
'Software: Android' => '63',
|
||||
'Software: Mac' => '64',
|
||||
'Software: Others' => '70',
|
||||
'TV: HD/X264/H264' => '41',
|
||||
'TV: SD/X264/H264' => '5',
|
||||
'TV: TV Packs' => '7',
|
||||
'TV: SD/XVID' => '50',
|
||||
'TV: Sport' => '72',
|
||||
'TV: HEVC/x265' => '77',
|
||||
'Unsorted: Unsorted' => '78'
|
||||
),
|
||||
'defaultValue' => '(ALL TYPES)'
|
||||
),
|
||||
'status' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Status',
|
||||
'values' => array(
|
||||
'Active Transfers' => '0',
|
||||
'Included Dead' => '1',
|
||||
'Only Dead' => '2'
|
||||
),
|
||||
'defaultValue' => 'Included Dead'
|
||||
),
|
||||
'lang' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Lang',
|
||||
'values' => array(
|
||||
'(ALL)' => '0',
|
||||
'Arabic' => '17',
|
||||
'Chinese ' => '10',
|
||||
'Danish' => '13',
|
||||
'Dutch' => '11',
|
||||
'English' => '1',
|
||||
'Finnish' => '18',
|
||||
'French' => '2',
|
||||
'German' => '3',
|
||||
'Greek' => '15',
|
||||
'Hindi' => '8',
|
||||
'Italian' => '4',
|
||||
'Japanese' => '5',
|
||||
'Korean' => '9',
|
||||
'Polish' => '14',
|
||||
'Russian' => '7',
|
||||
'Spanish' => '6',
|
||||
'Turkish' => '16'
|
||||
),
|
||||
'defaultValue' => '(ALL)'
|
||||
)
|
||||
));
|
||||
|
||||
protected $results_link;
|
||||
|
||||
public function collectData(){
|
||||
// No control on inputs, because all defaultValue are set
|
||||
$query_str = 'torrents-search.php';
|
||||
$query_str .= '?search=' . urlencode('+' . str_replace(' ', ' +', $this->getInput('query')));
|
||||
$query_str .= '&cat=' . $this->getInput('cat');
|
||||
$query_str .= '&incldead=' . $this->getInput('status');
|
||||
$query_str .= '&lang=' . $this->getInput('lang');
|
||||
$query_str .= '&sort=id&order=desc';
|
||||
|
||||
// Get results page
|
||||
$this->results_link = self::URI . $query_str;
|
||||
$html = getSimpleHTMLDOM($this->results_link);
|
||||
|
||||
// Loop on each entry
|
||||
foreach($html->find('table.table tr') as $element) {
|
||||
if($element->parent->tag == 'thead') continue;
|
||||
$entry = $element->find('td', 1)->find('a', 0);
|
||||
|
||||
// retrieve result page to get more details
|
||||
$link = rtrim(self::URI, '/') . $entry->href;
|
||||
$page = getSimpleHTMLDOM($link);
|
||||
|
||||
// get details & download links
|
||||
$details = $page->find('fieldset.download table', 0); // WHAT?? It should be the second one…
|
||||
$dllinks = $page->find('div#downloadbox table', 0);
|
||||
|
||||
// fill item
|
||||
$item = array();
|
||||
$item['author'] = $details->children(6)->children(1)->plaintext;
|
||||
$item['title'] = $entry->title;
|
||||
$item['uri'] = $link;
|
||||
$item['timestamp'] = strtotime($details->children(7)->children(1)->plaintext);
|
||||
$item['content'] = '';
|
||||
$item['content'] .= '<br/><b>Name: </b>' . $details->children(0)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Lang: </b>' . $details->children(3)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Size: </b>' . $details->children(4)->children(1)->innertext;
|
||||
$item['content'] .= '<br/><b>Hash: </b>' . $details->children(5)->children(1)->innertext;
|
||||
foreach($dllinks->children(0)->children(1)->find('a') as $dl) {
|
||||
$item['content'] .= '<br/>' . $dl->outertext;
|
||||
}
|
||||
$item['content'] .= '<br/><br/>' . $details->children(1)->children(0)->innertext;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
if($this->getInput('query')) {
|
||||
return '[' . self::NAME . '] ' . $this->getInput('query');
|
||||
}
|
||||
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
public function getURI(){
|
||||
if(isset($this->results_link) && !empty($this->results_link)) {
|
||||
return $this->results_link;
|
||||
}
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
}
|
@@ -3,64 +3,109 @@ class EZTVBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'alexAubin';
|
||||
const NAME = 'EZTV';
|
||||
const URI = 'https://eztv.ch/';
|
||||
const DESCRIPTION = 'Returns list of *recent* torrents for a specific show
|
||||
on EZTV. Get showID from URLs in https://eztv.ch/shows/showID/show-full-name.';
|
||||
const URI = 'https://eztv.re/';
|
||||
const DESCRIPTION = 'Returns list of torrents for specific show(s)
|
||||
on EZTV. Get IMDB IDs from IMDB.';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
'i' => array(
|
||||
'name' => 'Show ids',
|
||||
'exampleValue' => 'showID1,showID2,…',
|
||||
'required' => true
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'ids' => array(
|
||||
'name' => 'Show IMDB IDs',
|
||||
'exampleValue' => '8740790,1733785',
|
||||
'required' => true,
|
||||
'title' => 'One or more IMDB show IDs (can be found in the IMDB show URL)'
|
||||
),
|
||||
'no480' => array(
|
||||
'name' => 'No 480p',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to exclude 480p torrents'
|
||||
),
|
||||
'no720' => array(
|
||||
'name' => 'No 720p',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to exclude 720p torrents'
|
||||
),
|
||||
'no1080' => array(
|
||||
'name' => 'No 1080p',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to exclude 1080p torrents'
|
||||
),
|
||||
'no2160' => array(
|
||||
'name' => 'No 2160p',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to exclude 2160p torrents'
|
||||
),
|
||||
'noUnknownRes' => array(
|
||||
'name' => 'No Unknown resolution',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Activate to exclude unknown resolution torrents'
|
||||
),
|
||||
)
|
||||
));
|
||||
);
|
||||
|
||||
// Shamelessly lifted from https://stackoverflow.com/a/2510459
|
||||
protected function formatBytes($bytes, $precision = 2) {
|
||||
$units = array('B', 'KB', 'MB', 'GB', 'TB');
|
||||
|
||||
$bytes = max($bytes, 0);
|
||||
$pow = floor(($bytes ? log($bytes) : 0) / log(1024));
|
||||
$pow = min($pow, count($units) - 1);
|
||||
$bytes /= pow(1024, $pow);
|
||||
|
||||
return round($bytes, $precision) . ' ' . $units[$pow];
|
||||
}
|
||||
|
||||
protected function getItemFromTorrent($torrent){
|
||||
$item = array();
|
||||
$item['uri'] = $torrent->episode_url;
|
||||
$item['author'] = $torrent->imdb_id;
|
||||
$item['timestamp'] = date('d F Y H:i:s', $torrent->date_released_unix);
|
||||
$item['title'] = $torrent->title;
|
||||
$item['enclosures'][] = $torrent->torrent_url;
|
||||
|
||||
$thumbnailUri = 'https:' . $torrent->small_screenshot;
|
||||
$torrentSize = $this->formatBytes($torrent->size_bytes);
|
||||
|
||||
$item['content'] = $torrent->filename . '<br>File size: '
|
||||
. $torrentSize . '<br><a href="' . $torrent->magnet_url
|
||||
. '">magnet link</a><br><a href="' . $torrent->torrent_url
|
||||
. '">torrent link</a><br><img src="' . $thumbnailUri . '" />';
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private static function compareDate($torrent1, $torrent2) {
|
||||
return (strtotime($torrent1['timestamp']) < strtotime($torrent2['timestamp']) ? 1 : -1);
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$showIds = explode(',', $this->getInput('ids'));
|
||||
|
||||
// Make timestamp from relative released time in table
|
||||
function makeTimestamp($relativeReleaseTime){
|
||||
foreach($showIds as $showId) {
|
||||
$eztvUri = $this->getURI() . 'api/get-torrents?imdb_id=' . $showId;
|
||||
$content = getContents($eztvUri);
|
||||
$torrents = json_decode($content)->torrents;
|
||||
foreach($torrents as $torrent) {
|
||||
$title = $torrent->title;
|
||||
$regex480 = '/480p/';
|
||||
$regex720 = '/720p/';
|
||||
$regex1080 = '/1080p/';
|
||||
$regex2160 = '/2160p/';
|
||||
$regexUnknown = '/(480p|720p|1080p|2160p)/';
|
||||
// Skip unwanted resolution torrents
|
||||
if ((preg_match($regex480, $title) === 1 && $this->getInput('no480'))
|
||||
|| (preg_match($regex720, $title) === 1 && $this->getInput('no720'))
|
||||
|| (preg_match($regex1080, $title) === 1 && $this->getInput('no1080'))
|
||||
|| (preg_match($regex2160, $title) === 1 && $this->getInput('no2160'))
|
||||
|| (preg_match($regexUnknown, $title) !== 1 && $this->getInput('noUnknownRes'))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$relativeDays = 0;
|
||||
$relativeHours = 0;
|
||||
|
||||
foreach(explode(' ', $relativeReleaseTime) as $relativeTimeElement) {
|
||||
if(substr($relativeTimeElement, -1) == 'd') $relativeDays = substr($relativeTimeElement, 0, -1);
|
||||
if(substr($relativeTimeElement, -1) == 'h') $relativeHours = substr($relativeTimeElement, 0, -1);
|
||||
}
|
||||
return mktime(date('h') - $relativeHours, 0, 0, date('m'), date('d') - $relativeDays, date('Y'));
|
||||
}
|
||||
|
||||
// Loop on show ids
|
||||
$showList = explode(',', $this->getInput('i'));
|
||||
foreach($showList as $showID) {
|
||||
|
||||
// Get show page
|
||||
$html = getSimpleHTMLDOM(self::URI . 'shows/' . rawurlencode($showID) . '/');
|
||||
|
||||
// Loop on each element that look like an episode entry...
|
||||
foreach($html->find('.forum_header_border') as $element) {
|
||||
|
||||
// Filter entries that are not episode entries
|
||||
$ep = $element->find('td', 1);
|
||||
if(empty($ep)) continue;
|
||||
$epinfo = $ep->find('.epinfo', 0);
|
||||
$released = $element->find('td', 3);
|
||||
if(empty($epinfo)) continue;
|
||||
if(empty($released->plaintext)) continue;
|
||||
|
||||
// Filter entries that are older than 1 week
|
||||
if($released->plaintext == '>1 week') continue;
|
||||
|
||||
// Fill item
|
||||
$item = array();
|
||||
$item['uri'] = self::URI . $epinfo->href;
|
||||
$item['id'] = $item['uri'];
|
||||
$item['timestamp'] = makeTimestamp($released->plaintext);
|
||||
$item['title'] = $epinfo->plaintext;
|
||||
$item['content'] = $epinfo->alt;
|
||||
if(isset($item['title']))
|
||||
$this->items[] = $item;
|
||||
$this->items[] = $this->getItemFromTorrent($torrent);
|
||||
}
|
||||
}
|
||||
|
||||
// Sort all torrents in array by date
|
||||
usort($this->items, array('EZTVBridge', 'compareDate'));
|
||||
}
|
||||
}
|
||||
|
@@ -95,23 +95,38 @@ class EconomistBridge extends FeedExpander {
|
||||
|
||||
protected function parseItem($feedItem){
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
$article = getSimpleHTMLDOM($item['uri']);
|
||||
// before the article can be added, it needs to be cleaned up, thus, the extra function
|
||||
$item['content'] = $this->cleanContent($article);
|
||||
// We also need to distinguish between old style and new style articles
|
||||
if ($article->find('article', 0)->getAttribute('data-test-id') == 'Article') {
|
||||
$contentNode = 'div.layout-article-body';
|
||||
$imgNode = 'div.article__lead-image';
|
||||
$categoryNode = 'span.article__subheadline';
|
||||
} elseif ($article->find('article', 0)->getAttribute('data-test-id') === 'NewArticle') {
|
||||
$contentNode = 'section';
|
||||
$imgNode = 'figure.css-12eysrk.e3y6nua0';
|
||||
$categoryNode = 'span.ern1uyf0';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
$item['content'] = $this->cleanContent($article, $contentNode);
|
||||
// only the article lead image is retained if it's there
|
||||
if (!is_null($article->find('div.article__lead-image', 0))) {
|
||||
$item['enclosures'][] = $article->find('div.article__lead-image', 0)->find('img', 0)->getAttribute('src');
|
||||
if (!is_null($article->find($imgNode, 0))) {
|
||||
$item['enclosures'][] = $article->find($imgNode, 0)->find('img', 0)->getAttribute('src');
|
||||
} else {
|
||||
$item['enclosures'][] = '';
|
||||
}
|
||||
// add the subheadline as category. This will create a link in new articles
|
||||
// and a text in old articles
|
||||
$item['categories'][] = $article->find($categoryNode, 0)->innertext;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function cleanContent($article){
|
||||
private function cleanContent($article, $contentNode){
|
||||
// the actual article is in this div
|
||||
$content = $article->find('div.layout-article-body', 0)->innertext;
|
||||
$content = $article->find($contentNode, 0)->innertext;
|
||||
// clean the article content. Remove all div's since the text is in paragraph elements
|
||||
foreach (array(
|
||||
'<div '
|
||||
|
141
bridges/EconomistWorldInBriefBridge.php
Normal file
141
bridges/EconomistWorldInBriefBridge.php
Normal file
@@ -0,0 +1,141 @@
|
||||
<?php
|
||||
class EconomistWorldInBriefBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'sqrtminusone';
|
||||
const NAME = 'Economist the World in Brief Bridge';
|
||||
const URI = 'https://www.economist.com/the-world-in-brief';
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
const DESCRIPTION = 'Returns stories from the World in Brief section';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'' => array(
|
||||
'splitGobbets' => array(
|
||||
'name' => 'Split the short stories',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => false,
|
||||
'title' => 'Whether to split the short stories into separate entries'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Truncate headers for the short stories',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 100
|
||||
),
|
||||
'agenda' => array(
|
||||
'name' => 'Add agenda for the day',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'agendaPictures' => array(
|
||||
'name' => 'Include pictures to the agenda',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => 'checked'
|
||||
),
|
||||
'quote' => array(
|
||||
'name' => 'Include the quote of the day',
|
||||
'type' => 'checkbox'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
$gobbets = $html->find('._gobbets', 0);
|
||||
if ($this->getInput('splitGobbets') == 1) {
|
||||
$this->splitGobbets($gobbets);
|
||||
} else {
|
||||
$this->mergeGobbets($gobbets);
|
||||
};
|
||||
if ($this->getInput('agenda') == 1) {
|
||||
$articles = $html->find('._articles', 0);
|
||||
$this->collectArticles($articles);
|
||||
}
|
||||
if ($this->getInput('quote') == 1) {
|
||||
$quote = $html->find('._quote-container', 0);
|
||||
$this->addQuote($quote);
|
||||
}
|
||||
}
|
||||
|
||||
private function splitGobbets($gobbets)
|
||||
{
|
||||
$today = new Datetime();
|
||||
$today->setTime(0, 0, 0, 0);
|
||||
$limit = $this->getInput('limit');
|
||||
foreach ($gobbets->find('._gobbet') as $gobbet) {
|
||||
$title = $gobbet->plaintext;
|
||||
$match = preg_match('/[\.,]/', $title, $matches, PREG_OFFSET_CAPTURE);
|
||||
if ($match > 0) {
|
||||
$point = $matches[0][1];
|
||||
$title = mb_substr($title, 0, $point);
|
||||
}
|
||||
if ($limit && mb_strlen($title) > $limit) {
|
||||
$title = mb_substr($title, 0, $limit) . '...';
|
||||
}
|
||||
$item = array(
|
||||
'uri' => self::URI,
|
||||
'title' => $title,
|
||||
'content' => $gobbet->innertext,
|
||||
'timestamp' => $today->format('U'),
|
||||
'uid' => md5($gobbet->plaintext)
|
||||
);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function mergeGobbets($gobbets)
|
||||
{
|
||||
$today = new Datetime();
|
||||
$today->setTime(0, 0, 0, 0);
|
||||
$contents = '';
|
||||
foreach ($gobbets->find('._gobbet') as $gobbet) {
|
||||
$contents .= "<p>{$gobbet->innertext}";
|
||||
}
|
||||
$this->items[] = array(
|
||||
'uri' => self::URI,
|
||||
'title' => 'World in brief at ' . $today->format('Y.m.d'),
|
||||
'content' => $contents,
|
||||
'timestamp' => $today->format('U'),
|
||||
'uid' => 'world-in-brief-' . $today->format('U')
|
||||
);
|
||||
}
|
||||
|
||||
private function collectArticles($articles)
|
||||
{
|
||||
$i = 0;
|
||||
$today = new Datetime();
|
||||
$today->setTime(0, 0, 0, 0);
|
||||
foreach ($articles->find('._article') as $article) {
|
||||
$title = $article->find('._headline', 0)->plaintext;
|
||||
$image = $article->find('._main-image', 0);
|
||||
$content = $article->find('._content', 0);
|
||||
|
||||
$res_content = '';
|
||||
if ($image != null && $this->getInput('agendaPictures') == 1) {
|
||||
$img = $image->find('img', 0);
|
||||
$res_content .= '<img src="' . $img->src . '" />';
|
||||
}
|
||||
$res_content .= $content->innertext;
|
||||
$this->items[] = array(
|
||||
'uri' => self::URI,
|
||||
'title' => $title,
|
||||
'content' => $res_content,
|
||||
'timestamp' => $today->format('U'),
|
||||
'uid' => 'story-' . $today->format('U') . "{$i}",
|
||||
);
|
||||
$i++;
|
||||
}
|
||||
}
|
||||
|
||||
private function addQuote($quote) {
|
||||
$today = new Datetime();
|
||||
$today->setTime(0, 0, 0, 0);
|
||||
$this->items[] = array(
|
||||
'uri' => self::URI,
|
||||
'title' => 'Quote of the day ' . $today->format('Y.m.d'),
|
||||
'content' => $quote->innertext,
|
||||
'timestamp' => $today->format('U'),
|
||||
'uid' => 'quote-' . $today->format('U')
|
||||
);
|
||||
}
|
||||
}
|
@@ -12,6 +12,7 @@ class ElloBridge extends BridgeAbstract {
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'exampleValue' => 'zteph',
|
||||
'title' => 'Username'
|
||||
)
|
||||
),
|
||||
@@ -19,6 +20,7 @@ class ElloBridge extends BridgeAbstract {
|
||||
's' => array(
|
||||
'name' => 'Search',
|
||||
'required' => true,
|
||||
'exampleValue' => 'bird',
|
||||
'title' => 'Search'
|
||||
)
|
||||
)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class ElsevierBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'Pierre Mazière';
|
||||
const MAINTAINER = 'dvikan';
|
||||
const NAME = 'Elsevier journals recent articles';
|
||||
const URI = 'https://www.journals.elsevier.com/';
|
||||
const CACHE_TIMEOUT = 43200; //12h
|
||||
@@ -11,68 +11,31 @@ class ElsevierBridge extends BridgeAbstract {
|
||||
'j' => array(
|
||||
'name' => 'Journal name',
|
||||
'required' => true,
|
||||
'exampleValue' => 'academic-pediactrics',
|
||||
'exampleValue' => 'academic-pediatrics',
|
||||
'title' => 'Insert html-part of your journal'
|
||||
)
|
||||
));
|
||||
|
||||
// Extracts the list of names from an article as string
|
||||
private function extractArticleName($article){
|
||||
$names = $article->find('small', 0);
|
||||
if($names)
|
||||
return trim($names->plaintext);
|
||||
return '';
|
||||
}
|
||||
|
||||
// Extracts the timestamp from an article
|
||||
private function extractArticleTimestamp($article){
|
||||
$time = $article->find('.article-info', 0);
|
||||
if($time) {
|
||||
$timestring = trim($time->plaintext);
|
||||
/*
|
||||
The format depends on the age of an article:
|
||||
- Available online 29 July 2016
|
||||
- July 2016
|
||||
- May–June 2016
|
||||
*/
|
||||
if(preg_match('/\S*(\d+\s\S+\s\d{4})/ims', $timestring, $matches)) {
|
||||
return strtotime($matches[0]);
|
||||
} elseif (preg_match('/[A-Za-z]+\-([A-Za-z]+\s\d{4})/ims', $timestring, $matches)) {
|
||||
return strtotime($matches[0]);
|
||||
} elseif (preg_match('/([A-Za-z]+\s\d{4})/ims', $timestring, $matches)) {
|
||||
return strtotime($matches[0]);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Extracts the content from an article
|
||||
private function extractArticleContent($article){
|
||||
$content = $article->find('.article-content', 0);
|
||||
if($content) {
|
||||
return trim($content->plaintext);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.elsevier.io/verona/includes/favicons/favicon-32x32.png';
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$uri = self::URI . $this->getInput('j') . '/recent-articles/';
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
// Not all journals have the /recent-articles page
|
||||
$url = sprintf('https://www.journals.elsevier.com/%s/recent-articles/', $this->getInput('j'));
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
foreach($html->find('.pod-listing') as $article) {
|
||||
$item = array();
|
||||
$item['uri'] = $article->find('.pod-listing-header>a', 0)->getAttribute('href') . '?np=y';
|
||||
$item['title'] = $article->find('.pod-listing-header>a', 0)->plaintext;
|
||||
$item['author'] = $this->extractArticleName($article);
|
||||
$item['timestamp'] = $this->extractArticleTimestamp($article);
|
||||
$item['content'] = $this->extractArticleContent($article);
|
||||
foreach($html->find('article') as $recentArticle) {
|
||||
$item = [];
|
||||
$item['uri'] = $recentArticle->find('a', 0)->getAttribute('href');
|
||||
$item['title'] = $recentArticle->find('h2', 0)->plaintext;
|
||||
$item['author'] = $recentArticle->find('p > span', 0)->plaintext;
|
||||
$publicationDateString = trim($recentArticle->find('p > span', 1)->plaintext);
|
||||
$publicationDate = DateTimeImmutable::createFromFormat('F d, Y', $publicationDateString);
|
||||
if ($publicationDate) {
|
||||
$item['timestamp'] = $publicationDate->getTimestamp();
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon(): string {
|
||||
return 'https://cdn.elsevier.io/verona/includes/favicons/favicon-32x32.png';
|
||||
}
|
||||
}
|
||||
|
@@ -11,6 +11,7 @@ class EpicgamesBridge extends BridgeAbstract {
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 10,
|
||||
),
|
||||
|
@@ -12,7 +12,7 @@ class EtsyBridge extends BridgeAbstract {
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Insert your search term here',
|
||||
'exampleValue' => 'Enter your search term'
|
||||
'exampleValue' => 'lamp'
|
||||
),
|
||||
'queryextension' => array(
|
||||
'name' => 'Query extension',
|
||||
@@ -22,12 +22,10 @@ class EtsyBridge extends BridgeAbstract {
|
||||
(anything after ?search=<your search query>)',
|
||||
'exampleValue' => '&explicit=1&locationQuery=2921044'
|
||||
),
|
||||
'showimage' => array(
|
||||
'name' => 'Show image in content',
|
||||
'hideimage' => array(
|
||||
'name' => 'Hide image in content',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Activate to show the image in the content',
|
||||
'defaultValue' => 'checked'
|
||||
'title' => 'Activate to hide the image in the content',
|
||||
)
|
||||
)
|
||||
);
|
||||
@@ -35,29 +33,29 @@ class EtsyBridge extends BridgeAbstract {
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$results = $html->find('li.block-grid-item');
|
||||
$results = $html->find('li.wt-list-unstyled');
|
||||
|
||||
foreach($results as $result) {
|
||||
// Skip banner cards (ads for categories)
|
||||
if($result->find('span.ad-indicator'))
|
||||
// Remove Lazy loading
|
||||
if($result->find('.wt-skeleton-ui', 0))
|
||||
continue;
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $result->find('a', 0)->title;
|
||||
$item['uri'] = $result->find('a', 0)->href;
|
||||
$item['author'] = $result->find('p.text-gray-lighter', 0)->plaintext;
|
||||
$item['author'] = $result->find('p.wt-text-gray > span', 2)->plaintext;
|
||||
|
||||
$item['content'] = '<p>'
|
||||
. $result->find('span.currency-value', 0)->plaintext . ' '
|
||||
. $result->find('span.currency-symbol', 0)->plaintext
|
||||
. $result->find('span.currency-value', 0)->plaintext
|
||||
. '</p><p>'
|
||||
. $result->find('a', 0)->title
|
||||
. '</p>';
|
||||
|
||||
$image = $result->find('img.display-block', 0)->src;
|
||||
$image = $result->find('img.wt-display-block', 0)->src;
|
||||
|
||||
if($this->getInput('showimage')) {
|
||||
if(!$this->getInput('hideimage')) {
|
||||
$item['content'] .= '<img src="' . $image . '">';
|
||||
}
|
||||
|
||||
|
209
bridges/EuronewsBridge.php
Normal file
209
bridges/EuronewsBridge.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
class EuronewsBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'sqrtminusone';
|
||||
const NAME = 'Euronews Bridge';
|
||||
const URI = 'https://www.euronews.com/';
|
||||
const CACHE_TIMEOUT = 600; // 10 minutes
|
||||
const DESCRIPTION = 'Return articles from the "Just In" feed of Euronews.';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'' => array(
|
||||
'lang' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'defaultValue' => 'euronews.com',
|
||||
'values' => array(
|
||||
'English' => 'euronews.com',
|
||||
'French' => 'fr.euronews.com',
|
||||
'German' => 'de.euronews.com',
|
||||
'Italian' => 'it.euronews.com',
|
||||
'Spanish' => 'es.euronews.com',
|
||||
'Portuguese' => 'pt.euronews.com',
|
||||
'Russian' => 'ru.euronews.com',
|
||||
'Turkish' => 'tr.euronews.com',
|
||||
'Greek' => 'gr.euronews.com',
|
||||
'Hungarian' => 'hu.euronews.com',
|
||||
'Persian' => 'per.euronews.com',
|
||||
'Arabic' => 'arabic.euronews.com',
|
||||
/* These versions don't have timeline.json */
|
||||
// 'Albanian' => 'euronews.al',
|
||||
// 'Romanian' => 'euronews.ro',
|
||||
// 'Georigian' => 'euronewsgeorgia.com',
|
||||
// 'Bulgarian' => 'euronewsbulgaria.com'
|
||||
// 'Serbian' => 'euronews.rs'
|
||||
)
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit of items per feed',
|
||||
'required' => true,
|
||||
'type' => 'number',
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Maximum number of returned feed items. Maximum 50, default 10'
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$limit = $this->getInput('limit');
|
||||
$root_url = 'https://' . $this->getInput('lang');
|
||||
$url = $root_url . '/api/timeline.json?limit=' . $limit;
|
||||
$json = getContents($url);
|
||||
$data = json_decode($json, true);
|
||||
|
||||
foreach ($data as $datum) {
|
||||
$datum_uri = $root_url . $datum['fullUrl'];
|
||||
$url_datum = $this->getItemContent($datum_uri);
|
||||
$categories = array();
|
||||
if (array_key_exists('program', $datum)) {
|
||||
if (array_key_exists('title', $datum['program'])) {
|
||||
$categories[] = $datum['program']['title'];
|
||||
}
|
||||
}
|
||||
if (array_key_exists('themes', $datum)) {
|
||||
foreach ($datum['themes'] as $theme) {
|
||||
$categories[] = $theme['title'];
|
||||
}
|
||||
}
|
||||
$item = array(
|
||||
'uri' => $datum_uri,
|
||||
'title' => $datum['title'],
|
||||
'uid' => strval($datum['id']),
|
||||
'timestamp' => $datum['publishedAt'],
|
||||
'content' => $url_datum['content'],
|
||||
'author' => $url_datum['author'],
|
||||
'enclosures' => $url_datum['enclosures'],
|
||||
'categories' => array_unique($categories)
|
||||
);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getItemContent($url)
|
||||
{
|
||||
try {
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
} catch (Exception $e) {
|
||||
// Every once in a while it fails with too many redirects
|
||||
return array('author' => null, 'content' => null, 'enclosures' => null);
|
||||
}
|
||||
$data = $html->find('script[type="application/ld+json"]', 0)->innertext;
|
||||
$json = json_decode($data, true);
|
||||
$author = 'Euronews';
|
||||
$content = '';
|
||||
$enclosures = array();
|
||||
if (array_key_exists('@graph', $json)) {
|
||||
foreach ($json['@graph'] as $item) {
|
||||
if ($item['@type'] == 'NewsArticle') {
|
||||
if (array_key_exists('author', $item)) {
|
||||
$author = $item['author']['name'];
|
||||
}
|
||||
if (array_key_exists('image', $item)) {
|
||||
$content .= '<figure>';
|
||||
$content .= '<img src="' . $item['image']['url'] . '">';
|
||||
$content .= '<figcaption>' . $item['image']['caption'] . '</figcaption>';
|
||||
$content .= '</figure><br>';
|
||||
}
|
||||
if (array_key_exists('video', $item)) {
|
||||
$enclosures[] = $item['video']['contentUrl'];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Normal article
|
||||
$article_content = $html->find('.c-article-content', 0);
|
||||
if ($article_content) {
|
||||
// Usually the .c-article-content is the root of the
|
||||
// content, but once in a blue moon the root is the second
|
||||
// div
|
||||
if ((count($article_content->children()) == 2)
|
||||
&& ($article_content->children(1)->tag == 'div')
|
||||
) {
|
||||
$article_content = $article_content->children(1);
|
||||
}
|
||||
// The content is interspersed with links and stuff, so we
|
||||
// iterate over the children
|
||||
foreach ($article_content->children() as $element) {
|
||||
if ($element->tag == 'p') {
|
||||
$scribble_live = $element->find('#scribblelive-items', 0);
|
||||
if (is_null($scribble_live)) {
|
||||
// A normal paragraph
|
||||
$content .= '<p>' . $element->innertext . '</p>';
|
||||
} else {
|
||||
// LIVE mode
|
||||
foreach ($scribble_live->children() as $child) {
|
||||
if ($child->tag == 'div') {
|
||||
$content .= '<div>' . $child->innertext . '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
} elseif (preg_match('/h[1-6]/', $element->tag)) {
|
||||
// Header
|
||||
$content .= '<h' . $element->tag[1] . '>' . $element->innertext . '</h' . $element->tag[1] . '>';
|
||||
} elseif ($element->tag == 'div') {
|
||||
if (preg_match('/.*widget--type-image.*/', $element->class)) {
|
||||
// Image
|
||||
$content .= '<figure>';
|
||||
$content .= '<img src="' . $element->find('img', 0)->src . '">';
|
||||
$caption = $element->find('figcaption', 0);
|
||||
if ($caption) {
|
||||
$content .= '<figcaption>' . $element->plaintext . '</figcaption>';
|
||||
}
|
||||
$content .= '</figure><br>';
|
||||
} elseif (preg_match('/.*widget--type-quotation.*/', $element->class)) {
|
||||
// Quotation
|
||||
$quote = $element->find('.widget__quoteText', 0);
|
||||
$author = $element->find('.widget__author', 0);
|
||||
$content .= '<figure>';
|
||||
$content .= '<blockquote>' . $quote->plaintext . '</blockquote>';
|
||||
if ($author) {
|
||||
$content .= '<figcaption>' . $author->plaintext . '</figcaption>';
|
||||
}
|
||||
$content .= '</figure><br>';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Video article
|
||||
if (is_null($article_content)) {
|
||||
$image = $html->find('.c-article-media__img', 0);
|
||||
if ($image) {
|
||||
$content .= '<figure>';
|
||||
$content .= '<img src="' . $image->src . '">';
|
||||
$content .= '</figure><br>';
|
||||
}
|
||||
|
||||
$description = $html->find('.m-object__description', 0);
|
||||
if ($description) {
|
||||
// In some editions the description is a link to the
|
||||
// current page
|
||||
$content .= '<div>' . $description->plaintext . '</div>';
|
||||
}
|
||||
|
||||
// Euronews usually hosts videos on dailymotion...
|
||||
$player_div = $html->find('.dmPlayer', 0);
|
||||
if ($player_div) {
|
||||
$video_id = $player_div->getAttribute('data-video-id');
|
||||
$video_url = 'https://www.dailymotion.com/video/' . $video_id;
|
||||
$content .= '<a href="' . $video_url . '">' . $video_url . '</a>';
|
||||
}
|
||||
|
||||
// ...or on YouTube
|
||||
$player_div = $html->find('.js-player-pfp', 0);
|
||||
if ($player_div) {
|
||||
$video_id = $player_div->getAttribute('data-video-id');
|
||||
$video_url = 'https://www.youtube.com/watch?v=' . $video_id;
|
||||
$content .= '<a href="' . $video_url . '">' . $video_url . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
'author' => $author,
|
||||
'content' => $content,
|
||||
'enclosures' => $enclosures
|
||||
);
|
||||
}
|
||||
}
|
38
bridges/ExecuteProgramBridge.php
Normal file
38
bridges/ExecuteProgramBridge.php
Normal file
@@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
class ExecuteProgramBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Execute Program Blog';
|
||||
const URI = 'https://www.executeprogram.com/blog';
|
||||
const DESCRIPTION = 'Unofficial feed for the www.executeprogram.com blog';
|
||||
const MAINTAINER = 'dvikan';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$data = json_decode(getContents('https://www.executeprogram.com/api/pages/blog'));
|
||||
|
||||
foreach ($data->posts as $post) {
|
||||
$year = $post->date->year;
|
||||
$month = $post->date->month;
|
||||
$day = $post->date->day;
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = sprintf('https://www.executeprogram.com/blog/%s', $post->slug);
|
||||
$item['title'] = $post->title;
|
||||
$dateTime = \DateTime::createFromFormat('Y-m-d', $year . '-' . $month . '-' . $day);
|
||||
$item['timestamp'] = $dateTime->format('U');
|
||||
$item['content'] = $post->body;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
usort($this->items, function ($a, $b) {
|
||||
return $a['timestamp'] < $b['timestamp'];
|
||||
});
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.executeprogram.com/favicon.ico';
|
||||
}
|
||||
}
|
@@ -229,7 +229,7 @@ EOD
|
||||
|
||||
$ctx = stream_context_create(array(
|
||||
'http' => array(
|
||||
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||
'user_agent' => Configuration::getConfig('http', 'useragent'),
|
||||
'Accept' => 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8'
|
||||
)
|
||||
)
|
||||
@@ -254,7 +254,7 @@ EOD
|
||||
|
||||
$context = stream_context_create(array(
|
||||
'http' => array(
|
||||
'user_agent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:46.0) Gecko/20100101 Firefox/46.0',
|
||||
'user_agent' => Configuration::getConfig('http', 'useragent'),
|
||||
'header' => 'Cookie: ' . $cookies
|
||||
)
|
||||
)
|
||||
|
@@ -4,7 +4,7 @@ class FDroidBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'Mitsukarenai';
|
||||
const NAME = 'F-Droid Bridge';
|
||||
const URI = 'https://f-droid.org/';
|
||||
const CACHE_TIMEOUT = 60 * 60 * 2; // 2 hours
|
||||
const CACHE_TIMEOUT = 60 * 60 * 4; // 4 hours
|
||||
const DESCRIPTION = 'Returns latest added/updated apps on the open-source Android apps repository F-Droid';
|
||||
|
||||
const PARAMETERS = array( array(
|
||||
@@ -22,6 +22,32 @@ class FDroidBridge extends BridgeAbstract {
|
||||
return self::URI . 'assets/favicon.ico?v=8j6PKzW9Mk';
|
||||
}
|
||||
|
||||
private function getTimestamp($url) {
|
||||
$curlOptions = array(
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_HEADER => true,
|
||||
CURLOPT_NOBODY => true,
|
||||
CURLOPT_CONNECTTIMEOUT => 19,
|
||||
CURLOPT_TIMEOUT => 19,
|
||||
);
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, $curlOptions);
|
||||
$curlHeaders = curl_exec($ch);
|
||||
$curlError = curl_error($ch);
|
||||
curl_close($ch);
|
||||
if(!empty($curlError))
|
||||
return false;
|
||||
$curlHeaders = explode("\n", $curlHeaders);
|
||||
$timestamp = false;
|
||||
foreach($curlHeaders as $header) {
|
||||
if(strpos($header, 'Last-Modified') !== false) {
|
||||
$timestamp = str_replace('Last-Modified: ', '', $header);
|
||||
$timestamp = strtotime($timestamp);
|
||||
}
|
||||
}
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$url = self::URI;
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
@@ -45,6 +71,7 @@ class FDroidBridge extends BridgeAbstract {
|
||||
$item['uri'] = self::URI . $element->href;
|
||||
$item['title'] = $element->find('h4', 0)->plaintext;
|
||||
$item['icon'] = $element->find('img', 0)->src;
|
||||
$item['timestamp'] = $this->getTimestamp($item['icon']);
|
||||
$item['summary'] = $element->find('span.package-summary', 0)->plaintext;
|
||||
$item['content'] = '
|
||||
<a href="' . $item['uri'] . '">
|
||||
|
197
bridges/FDroidRepoBridge.php
Normal file
197
bridges/FDroidRepoBridge.php
Normal file
@@ -0,0 +1,197 @@
|
||||
<?php
|
||||
class FDroidRepoBridge extends BridgeAbstract {
|
||||
const NAME = 'F-Droid Repository Bridge';
|
||||
const URI = 'https://f-droid.org/';
|
||||
const DESCRIPTION = 'Query any F-Droid Repository for its latest updates.';
|
||||
const MAINTAINER = 'Yaman Qalieh';
|
||||
|
||||
const ITEM_LIMIT = 50;
|
||||
|
||||
const PARAMETERS = array(
|
||||
'global' => array(
|
||||
'url' => array(
|
||||
'name' => 'Repository URL',
|
||||
'title' => 'Usually ends with /repo/',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://srv.tt-rss.org/fdroid/repo'
|
||||
)
|
||||
),
|
||||
'Latest Updates' => array(
|
||||
'sorting' => array(
|
||||
'name' => 'Sort By',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Latest added apps' => 'added',
|
||||
'Latest updated apps' => 'lastUpdated'
|
||||
)
|
||||
),
|
||||
'locale' => array(
|
||||
'name' => 'Locale',
|
||||
'defaultValue' => 'en-US'
|
||||
)
|
||||
),
|
||||
'Follow Package' => array(
|
||||
'package' => array(
|
||||
'name' => 'Package Identifier',
|
||||
'required' => true,
|
||||
'exampleValue' => 'org.fox.ttrss'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Stores repo information
|
||||
private $repo;
|
||||
|
||||
public function getURI() {
|
||||
if (empty($this->queriedContext))
|
||||
return parent::getURI();
|
||||
|
||||
$url = rtrim($this->GetInput('url'), '/');
|
||||
return strstr($url, '?', true) ?: $url;
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
if (empty($this->queriedContext))
|
||||
return parent::getName();
|
||||
|
||||
$name = $this->repo['repo']['name'];
|
||||
switch($this->queriedContext) {
|
||||
case 'Latest Updates':
|
||||
return $name;
|
||||
case 'Follow Package':
|
||||
return $this->getInput('package') . ' - ' . $name;
|
||||
default:
|
||||
returnServerError('Unimplemented Context (getName)');
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->repo = $this->getRepo();
|
||||
switch($this->queriedContext) {
|
||||
case 'Latest Updates':
|
||||
$this->getAllUpdates();
|
||||
break;
|
||||
case 'Follow Package':
|
||||
$this->getPackage($this->getInput('package'));
|
||||
break;
|
||||
default:
|
||||
returnServerError('Unimplemented Context (collectData)');
|
||||
}
|
||||
}
|
||||
|
||||
private function getRepo() {
|
||||
$url = $this->getURI();
|
||||
|
||||
// Get repo information (only available as JAR)
|
||||
$jar = getContents($url . '/index-v1.jar');
|
||||
$jar_loc = tempnam(sys_get_temp_dir(), '');
|
||||
file_put_contents($jar_loc, $jar);
|
||||
|
||||
// JAR files are specially formatted ZIP files
|
||||
$jar = new ZipArchive;
|
||||
if ($jar->open($jar_loc) !== true) {
|
||||
returnServerError('Failed to extract archive');
|
||||
}
|
||||
|
||||
// Get file pointer to the relevant JSON inside
|
||||
$fp = $jar->getStream('index-v1.json');
|
||||
if (!$fp) {
|
||||
returnServerError('Failed to get file pointer');
|
||||
}
|
||||
|
||||
$data = json_decode(stream_get_contents($fp), true);
|
||||
fclose($fp);
|
||||
$jar->close();
|
||||
return $data;
|
||||
}
|
||||
|
||||
private function getAllUpdates() {
|
||||
$apps = $this->repo['apps'];
|
||||
usort($apps, function($a, $b) {
|
||||
return $b[$this->getInput('sorting')] <=> $a[$this->getInput('sorting')];
|
||||
});
|
||||
$apps = array_slice($apps, 0, self::ITEM_LIMIT);
|
||||
foreach($apps as $app) {
|
||||
$latest = reset($this->repo['packages'][$app['packageName']]);
|
||||
|
||||
if (isset($app['localized'])) {
|
||||
// Try provided locale, then en-US, then any
|
||||
$lang = $app['localized'];
|
||||
$lang = $lang[$this->getInput('locale')] ?? $lang['en-US'] ?? reset($lang);
|
||||
} else
|
||||
$lang = array();
|
||||
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '/' . $latest['apkName'];
|
||||
$item['title'] = $lang['name'] ?? $app['packageName'];
|
||||
$item['title'] .= ' ' . $latest['versionName'];
|
||||
$item['timestamp'] = date(DateTime::ISO8601, (int) ($app['lastUpdated'] / 1000));
|
||||
if (isset($app['authorName']))
|
||||
$item['author'] = $app['authorName'];
|
||||
if (isset($app['categories']))
|
||||
$item['categories'] = $app['categories'];
|
||||
|
||||
// Adding Content
|
||||
$icon = $app['icon'] ?? '';
|
||||
if (!empty($icon)) {
|
||||
$icon = $this->getURI() . '/icons-320/' . $icon;
|
||||
$item['enclosures'] = array($icon);
|
||||
$icon = '<img src="' . $icon . '">';
|
||||
}
|
||||
$summary = $lang['summary'] ?? $app['summary'] ?? '';
|
||||
$description = markdownToHtml(trim($lang['description'] ?? $app['description'] ?? 'None'));
|
||||
$whatsNew = markdownToHtml(trim($lang['whatsNew'] ?? 'None'));
|
||||
$website = $this->link($lang['webSite'] ?? $app['webSite'] ?? $app['authorWebSite'] ?? null);
|
||||
$source = $this->link($app['sourceCode'] ?? null);
|
||||
$issueTracker = $this->link($app['issueTracker'] ?? null);
|
||||
$license = $app['license'] ?? 'None';
|
||||
$item['content'] = <<<EOD
|
||||
{$icon}
|
||||
<p>{$summary}</p>
|
||||
<h1>Description</h1>
|
||||
{$description}
|
||||
<h1>What's New</h1>
|
||||
{$whatsNew}
|
||||
<h1>Information</h1>
|
||||
<p>Website: {$website}</p>
|
||||
<p>Source Code: {$source}</p>
|
||||
<p>Issue Tracker: {$issueTracker}</p>
|
||||
<p>license: {$app['license']}</p>
|
||||
EOD;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function getPackage($package) {
|
||||
if (!isset($this->repo['packages'][$package])) {
|
||||
returnClientError('Invalid Package Name');
|
||||
}
|
||||
$package = $this->repo['packages'][$package];
|
||||
|
||||
$count = self::ITEM_LIMIT;
|
||||
foreach($package as $version) {
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '/' . $version['apkName'];
|
||||
$item['title'] = $version['versionName'];
|
||||
$item['timestamp'] = date(DateTime::ISO8601, (int) ($version['added'] / 1000));
|
||||
$item['uid'] = $version['versionCode'];
|
||||
$size = round($version['size'] / 1048576, 1); // Bytes -> MB
|
||||
$sdk_link = 'https://developer.android.com/studio/releases/platforms';
|
||||
$item['content'] = <<<EOD
|
||||
<p>size: {$size}MB</p>
|
||||
<p>Minimum SDK: {$version['minSdkVersion']}
|
||||
(<a href="{$sdk_link}">SDK to Android Version List</a>)</p>
|
||||
<p>hash ({$version['hashType']}): {$version['hash']}</p>
|
||||
EOD;
|
||||
$this->items[] = $item;
|
||||
if (--$count <= 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function link($url) {
|
||||
if (empty($url))
|
||||
return null;
|
||||
return '<a href="' . $url . '">' . $url . '</a>';
|
||||
}
|
||||
}
|
@@ -13,11 +13,12 @@ class FSecureBlogBridge extends BridgeAbstract {
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'required' => true,
|
||||
'defaultValue' => 'en',
|
||||
),
|
||||
'oldest_date' => array(
|
||||
'name' => 'Oldest article date',
|
||||
'exampleValue' => '-2 months',
|
||||
'exampleValue' => '-6 months',
|
||||
),
|
||||
)
|
||||
);
|
||||
|
54
bridges/FeedMergeBridge.php
Normal file
54
bridges/FeedMergeBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
class FeedMergeBridge extends FeedExpander {
|
||||
const MAINTAINER = 'dvikan';
|
||||
const NAME = 'FeedMerge';
|
||||
const URI = 'https://github.com/RSS-Bridge/rss-bridge';
|
||||
const DESCRIPTION = <<<'TEXT'
|
||||
This bridge merges two or more feeds into a single feed. Max 10 items are fetched from each feed.
|
||||
TEXT;
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'feed_name' => [
|
||||
'name' => 'Feed name',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'rss-bridge/FeedMerger',
|
||||
],
|
||||
'feed_1' => [
|
||||
'name' => 'Feed url',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://lorem-rss.herokuapp.com/feed?unit=day'
|
||||
],
|
||||
'feed_2' => ['name' => 'Feed url', 'type' => 'text'],
|
||||
'feed_3' => ['name' => 'Feed url', 'type' => 'text'],
|
||||
'feed_4' => ['name' => 'Feed url', 'type' => 'text'],
|
||||
'feed_5' => ['name' => 'Feed url', 'type' => 'text'],
|
||||
]
|
||||
];
|
||||
|
||||
public function collectData() {
|
||||
$limit = 10;
|
||||
$feeds = [
|
||||
$this->getInput('feed_1'),
|
||||
$this->getInput('feed_2'),
|
||||
$this->getInput('feed_3'),
|
||||
$this->getInput('feed_4'),
|
||||
$this->getInput('feed_5'),
|
||||
];
|
||||
// Remove empty values
|
||||
$feeds = array_filter($feeds);
|
||||
foreach ($feeds as $feed) {
|
||||
$this->collectExpandableDatas($feed, $limit);
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://cdn.jsdelivr.net/npm/famfamfam-silk@1.0.0/dist/png/folder_feed.png';
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
return $this->getInput('feed_name') ?: 'rss-bridge/FeedMerger';
|
||||
}
|
||||
}
|
60
bridges/FeedReducerBridge.php
Normal file
60
bridges/FeedReducerBridge.php
Normal file
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
class FeedReducerBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'mdemoss';
|
||||
const NAME = 'Feed Reducer';
|
||||
const URI = 'http://github.com/RSS-Bridge/rss-bridge/';
|
||||
const DESCRIPTION = 'Choose a percentage of a feed you want to see.';
|
||||
const PARAMETERS = array( array(
|
||||
'url' => array(
|
||||
'name' => 'Feed URI',
|
||||
'exampleValue' => 'https://lorem-rss.herokuapp.com/feed?length=42',
|
||||
'required' => true
|
||||
),
|
||||
'percentage' => array(
|
||||
'name' => 'percentage',
|
||||
'type' => 'number',
|
||||
'exampleValue' => 50,
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
public function collectData(){
|
||||
if(preg_match('#^http(s?)://#i', $this->getInput('url'))) {
|
||||
$this->collectExpandableDatas($this->getInput('url'));
|
||||
} else {
|
||||
throw new Exception('URI must begin with http(s)://');
|
||||
}
|
||||
}
|
||||
|
||||
public function getItems(){
|
||||
$filteredItems = array();
|
||||
$intPercentage = (int)preg_replace('/[^0-9]/', '', $this->getInput('percentage'));
|
||||
|
||||
foreach ($this->items as $thisItem) {
|
||||
// The URL is included in the hash:
|
||||
// - so you can change the output by adding a local-part to the URL
|
||||
// - so items with the same URI in different feeds won't be correlated
|
||||
|
||||
// $pseudoRandomInteger will be a 16 bit unsigned int mod 100.
|
||||
// This won't be uniformly distributed 1-100, but should be close enough.
|
||||
|
||||
$pseudoRandomInteger = unpack(
|
||||
'S', // unsigned 16-bit int
|
||||
hash( 'sha256', $thisItem['uri'] . '::' . $this->getInput('url'), true )
|
||||
)[1] % 100;
|
||||
|
||||
if ($pseudoRandomInteger < $intPercentage) {
|
||||
$filteredItems[] = $thisItem;
|
||||
}
|
||||
}
|
||||
|
||||
return $filteredItems;
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
$trimmedPercentage = preg_replace('/[^0-9]/', '', $this->getInput('percentage') ?? '');
|
||||
return parent::getName() . ' [' . $trimmedPercentage . '%]';
|
||||
}
|
||||
}
|
@@ -11,6 +11,8 @@ class FilterBridge extends FeedExpander {
|
||||
const PARAMETERS = array(array(
|
||||
'url' => array(
|
||||
'name' => 'Feed URL',
|
||||
'type' => 'text',
|
||||
'defaultValue' => 'https://lorem-rss.herokuapp.com/feed?unit=day',
|
||||
'required' => true,
|
||||
),
|
||||
'filter' => array(
|
||||
|
@@ -26,7 +26,8 @@ class FindACrewBridge extends BridgeAbstract {
|
||||
'distance' => array(
|
||||
'name' => 'Limit boundary of search in KM',
|
||||
'title' => 'Boundary of the search in kilometers when using longitude and latitude'
|
||||
)
|
||||
),
|
||||
'limit' => self::LIMIT,
|
||||
)
|
||||
);
|
||||
|
||||
@@ -59,14 +60,15 @@ class FindACrewBridge extends BridgeAbstract {
|
||||
$html = getSimpleHTMLDOM($url, $header, $opts) or returnClientError('No results for this query.');
|
||||
|
||||
$annonces = $html->find('.css_SrhRst');
|
||||
foreach ($annonces as $annonce) {
|
||||
$limit = $this->getInput('limit') ?? 10;
|
||||
foreach (array_slice($annonces, 0, $limit) as $annonce) {
|
||||
$item = array();
|
||||
|
||||
$link = parent::getURI() . $annonce->find('.lst-ctrls a', 0)->href;
|
||||
$link = parent::getURI() . $annonce->find('.lstsum-btn-con a', 0)->href;
|
||||
$htmlDetail = getSimpleHTMLDOMCached($link . '?mdl=2'); // add ?mdl=2 for xhr content not full html page
|
||||
|
||||
$img = parent::getURI() . $htmlDetail->find('img.img-responsive', 0)->getAttribute('src');
|
||||
$item['title'] = $annonce->find('.lst-tags span', 0)->plaintext;
|
||||
$item['title'] = $htmlDetail->find('div.label-account', 0)->plaintext;
|
||||
$item['uri'] = $link;
|
||||
$content = $htmlDetail->find('.panel-body div.clearfix.row > div', 1)->innertext;
|
||||
$content .= $htmlDetail->find('.panel-body > div', 1)->innertext;
|
||||
|
@@ -51,6 +51,15 @@ class FlickrBridge extends BridgeAbstract {
|
||||
'title' => 'Insert username (as shown in the address bar)',
|
||||
'exampleValue' => 'flickr'
|
||||
),
|
||||
'content' => array(
|
||||
'name' => 'Content',
|
||||
'type' => 'list',
|
||||
'values' => array(
|
||||
'Uploads' => 'uploads',
|
||||
'Favorites' => 'faves',
|
||||
),
|
||||
'defaultValue' => 'uploads',
|
||||
),
|
||||
'media' => array(
|
||||
'name' => 'Media',
|
||||
'type' => 'list',
|
||||
@@ -156,8 +165,14 @@ class FlickrBridge extends BridgeAbstract {
|
||||
. '&sort=' . $this->getInput('sort') . '&media=' . $this->getInput('media');
|
||||
break;
|
||||
case 'By username':
|
||||
return self::URI . 'search/?user_id=' . urlencode($this->getInput('u'))
|
||||
. '&sort=' . $this->getInput('sort') . '&media=' . $this->getInput('media');
|
||||
$uri = self::URI . 'search/?user_id=' . urlencode($this->getInput('u'))
|
||||
. '&sort=date-posted-desc&media=' . $this->getInput('media');
|
||||
|
||||
if ($this->getInput('content') === 'faves') {
|
||||
return $uri . '&faves=1';
|
||||
}
|
||||
|
||||
return $uri;
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -175,6 +190,11 @@ class FlickrBridge extends BridgeAbstract {
|
||||
return $this->getInput('q') . ' - keyword - ' . self::NAME;
|
||||
break;
|
||||
case 'By username':
|
||||
|
||||
if ($this->getInput('content') === 'faves') {
|
||||
return $this->username . ' - favorites - ' . self::NAME;
|
||||
}
|
||||
|
||||
return $this->username . ' - ' . self::NAME;
|
||||
break;
|
||||
|
||||
|
@@ -9,29 +9,45 @@ class FolhaDeSaoPauloBridge extends FeedExpander {
|
||||
'feed' => array(
|
||||
'name' => 'Feed sub-URL',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Select the sub-feed (see https://www1.folha.uol.com.br/feed/)',
|
||||
'exampleValue' => 'emcimadahora/rss091.xml',
|
||||
)
|
||||
),
|
||||
'amount' => array(
|
||||
'name' => 'Amount of items to fetch',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 15,
|
||||
),
|
||||
'deep_crawl' => array(
|
||||
'name' => 'Deep Crawl',
|
||||
'description' => 'Crawl each item "deeply", that is, return the article contents',
|
||||
'type' => 'checkbox',
|
||||
'defaultValue' => true,
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$articleHTMLContent = getSimpleHTMLDOMCached($item['uri']);
|
||||
if($articleHTMLContent) {
|
||||
foreach ($articleHTMLContent->find('div.c-news__body .is-hidden') as $toRemove) {
|
||||
$toRemove->innertext = '';
|
||||
}
|
||||
$item_content = $articleHTMLContent->find('div.c-news__body', 0);
|
||||
if ($item_content) {
|
||||
$text = $item_content->innertext;
|
||||
$text = strip_tags($text, '<p><b><a><blockquote><figure><figcaption><img><strong><em>');
|
||||
$item['content'] = $text;
|
||||
$item['uri'] = explode('*', $item['uri'])[1];
|
||||
if ($this->getInput('deep_crawl')) {
|
||||
$articleHTMLContent = getSimpleHTMLDOMCached($item['uri']);
|
||||
if($articleHTMLContent) {
|
||||
foreach ($articleHTMLContent->find('div.c-news__body .is-hidden') as $toRemove) {
|
||||
$toRemove->innertext = '';
|
||||
}
|
||||
$item_content = $articleHTMLContent->find('div.c-news__body', 0);
|
||||
if ($item_content) {
|
||||
$text = $item_content->innertext;
|
||||
$text = strip_tags($text, '<p><b><a><blockquote><figure><figcaption><img><strong><em><ul><li>');
|
||||
$item['content'] = $text;
|
||||
$item['uri'] = explode('*', $item['uri'])[1];
|
||||
}
|
||||
} else {
|
||||
Debug::log('???: ' . $item['uri']);
|
||||
}
|
||||
} else {
|
||||
Debug::log('???: ' . $item['uri']);
|
||||
$item['uri'] = explode('*', $item['uri'])[1];
|
||||
}
|
||||
|
||||
return $item;
|
||||
@@ -47,6 +63,7 @@ class FolhaDeSaoPauloBridge extends FeedExpander {
|
||||
$feed_url = self::URI . '/' . $this->getInput('feed');
|
||||
}
|
||||
Debug::log('URL: ' . $feed_url);
|
||||
$this->collectExpandableDatas($feed_url);
|
||||
$limit = $this->getInput('amount');
|
||||
$this->collectExpandableDatas($feed_url, $limit);
|
||||
}
|
||||
}
|
||||
|
@@ -1,74 +0,0 @@
|
||||
<?php
|
||||
class FootitoBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'superbaillot.net';
|
||||
const NAME = 'Footito';
|
||||
const URI = 'http://www.footito.fr/';
|
||||
const DESCRIPTION = 'Footito';
|
||||
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach($html->find('div.post') as $element) {
|
||||
$item = array();
|
||||
|
||||
$content = trim($element->innertext);
|
||||
$content = str_replace(
|
||||
'<img',
|
||||
"<img style='float : left;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="logo"',
|
||||
"style='float : left;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="contenu"',
|
||||
"style='margin-left : 60px;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="responsive-comment"',
|
||||
"style='border-top : 1px #DDD solid; background-color : white; padding : 10px;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="jaime"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="auteur-event responsive"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="report-abuse-button"',
|
||||
"style='display : none;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="reaction clearfix"',
|
||||
"style='margin : 10px 0px; padding : 5px; border-bottom : 1px #DDD solid;'",
|
||||
$content );
|
||||
|
||||
$content = str_replace(
|
||||
'class="infos"',
|
||||
"style='font-size : 0.7em;'",
|
||||
$content );
|
||||
|
||||
$item['content'] = $content;
|
||||
|
||||
$title = $element->find('.contenu .texte ', 0)->plaintext;
|
||||
$item['title'] = $title;
|
||||
|
||||
$info = $element->find('div.infos', 0);
|
||||
|
||||
$item['timestamp'] = strtotime($info->find('time', 0)->datetime);
|
||||
$item['author'] = $info->find('a.auteur', 0)->plaintext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -10,11 +10,13 @@ class FourchanBridge extends BridgeAbstract {
|
||||
const PARAMETERS = array( array(
|
||||
'c' => array(
|
||||
'name' => 'Thread category',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'po',
|
||||
),
|
||||
't' => array(
|
||||
'name' => 'Thread number',
|
||||
'type' => 'number',
|
||||
'exampleValue' => '597271',
|
||||
'required' => true
|
||||
)
|
||||
));
|
||||
|
84
bridges/FunkBridge.php
Normal file
84
bridges/FunkBridge.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
class FunkBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'µKöff';
|
||||
const NAME = 'Funk';
|
||||
const URI = 'https://www.funk.net/';
|
||||
const DESCRIPTION = 'Videos per channel of German public video-on-demand service Funk';
|
||||
|
||||
const PARAMETERS = array(
|
||||
'Channel' => array(
|
||||
'channel' => array(
|
||||
'name' => 'Slug',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'game-two-856'
|
||||
),
|
||||
'max' => array(
|
||||
'name' => 'Maximum',
|
||||
'type' => 'number',
|
||||
'defaultValue' => '-1'
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Channel':
|
||||
$url = static::URI . 'data/videos/byChannelAlias/' . $this->getInput('channel') . '/';
|
||||
if(!empty($this->getInput('max')) && $this->getInput('max') >= 0) {
|
||||
$url .= '?size=' . $this->getInput('max');
|
||||
}
|
||||
|
||||
$jsonString = getContents($url) or returnServerError('No contents received!');
|
||||
$json = json_decode($jsonString, true);
|
||||
|
||||
foreach($json['list'] as $element) {
|
||||
$this->items[] = $this->collectArticle($element);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
returnServerError('Unknown context!');
|
||||
}
|
||||
}
|
||||
|
||||
private function collectArticle($element) {
|
||||
$item = array();
|
||||
$item['uri'] = static::URI . 'channel/' . $element['channelAlias'] . '/' . $element['alias'];
|
||||
$item['title'] = $element['title'];
|
||||
$item['timestamp'] = $element['publicationDate'];
|
||||
$item['author'] = str_replace('-' . $element['channelId'], '', $element['channelAlias']);
|
||||
$item['content'] = $element['shortDescription'];
|
||||
$item['enclosures'] = array(
|
||||
'https://www.funk.net/api/v4.0/thumbnails/' . $element['imageLandscape']
|
||||
);
|
||||
$item['uid'] = $element['entityId'];
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function detectParameters($url) {
|
||||
$regex = '/^https?:\/\/(?:www\.)?funk\.net\/channel\/([^\/]+).*$/';
|
||||
if(preg_match($regex, $url, $urlMatches) > 0) {
|
||||
return array(
|
||||
'channel' => $urlMatches[1]
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon() {
|
||||
return 'https://www.funk.net/img/favicons/favicon-192x192.png';
|
||||
}
|
||||
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'Channel':
|
||||
if(!empty($this->getInput('channel'))) {
|
||||
return $this->getInput('channel');
|
||||
}
|
||||
break;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
@@ -9,7 +9,8 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'Search' => array(
|
||||
'q' => array(
|
||||
'name' => 'Query',
|
||||
'required' => true
|
||||
'required' => true,
|
||||
'exampleValue' => 'dog',
|
||||
),
|
||||
'rating-general' => array(
|
||||
'name' => 'General',
|
||||
@@ -79,6 +80,7 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
@@ -449,6 +451,7 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'username-journals' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'exampleValue' => 'dhw',
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
@@ -463,6 +466,7 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'journal-id' => array(
|
||||
'name' => 'Journal ID',
|
||||
'required' => true,
|
||||
'exampleValue' => '10008853',
|
||||
'type' => 'number',
|
||||
'title' => 'Number seen in journal URL'
|
||||
)
|
||||
@@ -471,11 +475,13 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'username-gallery' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'exampleValue' => 'dhw',
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
@@ -496,11 +502,13 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'username-scraps' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'exampleValue' => 'dhw',
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
@@ -521,11 +529,13 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'username-favorites' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'exampleValue' => 'dhw',
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
@@ -546,17 +556,20 @@ class FurAffinityBridge extends BridgeAbstract {
|
||||
'username-folder' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'exampleValue' => 'kopk',
|
||||
'title' => 'Lowercase username as seen in URLs'
|
||||
),
|
||||
'folder-id' => array(
|
||||
'name' => 'Folder ID',
|
||||
'required' => true,
|
||||
'exampleValue' => '1031990',
|
||||
'type' => 'number',
|
||||
'title' => 'Number seen in folder URL'
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 10,
|
||||
'title' => 'Limit number of submissions to return. -1 for unlimited.'
|
||||
),
|
||||
|
@@ -3,21 +3,23 @@ class FurAffinityUserBridge extends BridgeAbstract {
|
||||
const NAME = 'FurAffinity User Gallery';
|
||||
const URI = 'https://www.furaffinity.net';
|
||||
const MAINTAINER = 'CyberJacob';
|
||||
const DESCRIPTION = 'See https://rss-bridge.github.io/rss-bridge/Bridge_Specific/Furaffinityuser.html for explanation';
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'searchUsername' => array(
|
||||
'name' => 'Search Username',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'title' => 'Username to fetch the gallery for'
|
||||
'title' => 'Username to fetch the gallery for',
|
||||
'exampleValue' => 'armundy',
|
||||
),
|
||||
'loginUsername' => array(
|
||||
'name' => 'Login Username',
|
||||
'aCookie' => array(
|
||||
'name' => 'Login cookie \'a\'',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
),
|
||||
'loginPassword' => array(
|
||||
'name' => 'Login Password',
|
||||
'bCookie' => array(
|
||||
'name' => 'Login cookie \'b\'',
|
||||
'type' => 'text',
|
||||
'required' => true
|
||||
)
|
||||
@@ -25,10 +27,12 @@ class FurAffinityUserBridge extends BridgeAbstract {
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
$cookies = self::login();
|
||||
$opt = array(CURLOPT_COOKIE => 'b=' . $this->getInput('bCookie') . '; a=' . $this->getInput('aCookie'));
|
||||
|
||||
$url = self::URI . '/gallery/' . $this->getInput('searchUsername');
|
||||
|
||||
$html = getSimpleHTMLDOM($url, $cookies);
|
||||
$html = getSimpleHTMLDOM($url, array(), $opt)
|
||||
or returnServerError('Could not load the user\'s gallery page.');
|
||||
|
||||
$submissions = $html->find('section[id=gallery-gallery]', 0)->find('figure');
|
||||
foreach($submissions as $submission) {
|
||||
@@ -51,59 +55,4 @@ class FurAffinityUserBridge extends BridgeAbstract {
|
||||
public function getURI() {
|
||||
return self::URI . '/user/' . $this->getInput('searchUsername');
|
||||
}
|
||||
|
||||
private function login() {
|
||||
$ch = curl_init(self::URI . '/login/');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, ini_get('user_agent'));
|
||||
curl_setopt($ch, CURLOPT_ENCODING, '');
|
||||
curl_setopt($ch, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
|
||||
|
||||
$fields = implode('&', array(
|
||||
'action=login',
|
||||
'retard_protection=1',
|
||||
'name=' . urlencode($this->getInput('loginUsername')),
|
||||
'pass=' . urlencode($this->getInput('loginPassword')),
|
||||
'login=Login to Faraffinity'
|
||||
));
|
||||
|
||||
curl_setopt($ch, CURLOPT_POST, 5);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
|
||||
|
||||
if(defined('PROXY_URL') && !defined('NOPROXY')) {
|
||||
curl_setopt($ch, CURLOPT_PROXY, PROXY_URL);
|
||||
}
|
||||
|
||||
curl_setopt($ch, CURLOPT_HEADER, true);
|
||||
curl_setopt($ch, CURLINFO_HEADER_OUT, true);
|
||||
|
||||
$data = curl_exec($ch);
|
||||
|
||||
$errorCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
|
||||
$curlError = curl_error($ch);
|
||||
$curlErrno = curl_errno($ch);
|
||||
$curlInfo = curl_getinfo($ch);
|
||||
|
||||
if($data === false)
|
||||
fDebug::log("Cant't download {$url} cUrl error: {$curlError} ({$curlErrno})");
|
||||
|
||||
curl_close($ch);
|
||||
|
||||
if($errorCode != 200) {
|
||||
returnServerError(error_get_last());
|
||||
} else {
|
||||
preg_match_all('/^Set-Cookie:\s*([^;]*)/mi', $data, $matches);
|
||||
$cookies = array();
|
||||
|
||||
foreach($matches[1] as $item) {
|
||||
parse_str($item, $cookie);
|
||||
$cookies = array_merge($cookies, $cookie);
|
||||
}
|
||||
|
||||
return $cookies;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -32,45 +32,48 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function decodeHtmlEntities($text) {
|
||||
$text = html_entity_decode($text);
|
||||
$convmap = array(0x0, 0x2FFFF, 0, 0xFFFF);
|
||||
return trim(mb_decode_numericentity($text, $convmap, 'UTF-8'));
|
||||
}
|
||||
|
||||
private function cleanupPostContent($content, $site_url){
|
||||
$content = str_replace(':arrow:', '➤', $content);
|
||||
$content = str_replace('href="attachments/', 'href="' . $site_url . 'attachments/', $content);
|
||||
$content = defaultLinkTo($content, self::URI);
|
||||
$content = stripWithDelimiters($content, '<script', '</script>');
|
||||
return $content;
|
||||
$content = stripWithDelimiters($content, '<svg', '</svg>');
|
||||
$content = stripRecursiveHTMLSection($content, 'div', '<div class="reactionsBar');
|
||||
return $this->decodeHtmlEntities($content);
|
||||
}
|
||||
|
||||
private function findItemDate($item){
|
||||
$time = 0;
|
||||
$dateField = $item->find('abbr.DateTime', 0);
|
||||
$dateField = $item->find('time', 0);
|
||||
if (is_object($dateField)) {
|
||||
$time = intval(
|
||||
extractFromDelimiters(
|
||||
$dateField->outertext,
|
||||
'data-time="',
|
||||
'"'
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$dateField = $item->find('span.DateTime', 0);
|
||||
$time = DateTime::createFromFormat(
|
||||
'M j, Y \a\t g:i A',
|
||||
extractFromDelimiters(
|
||||
$dateField->outertext,
|
||||
'title="',
|
||||
'"'
|
||||
)
|
||||
)->getTimestamp();
|
||||
$time = strtotime($dateField->datetime);
|
||||
}
|
||||
return $time;
|
||||
}
|
||||
|
||||
private function findItemImage($item, $selector){
|
||||
$img = extractFromDelimiters($item->find($selector, 0)->style, 'url(', ')');
|
||||
$paramPos = strpos($img, '?');
|
||||
if ($paramPos !== false) {
|
||||
$img = substr($img, 0, $paramPos);
|
||||
}
|
||||
if (!str_ends_with($img, '.png') && !str_ends_with($img, '.jpg')) {
|
||||
$img = $img . '#.image';
|
||||
}
|
||||
return urljoin(self::URI, $img);
|
||||
}
|
||||
|
||||
private function fetchPostContent($uri, $site_url){
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
if(!$html) {
|
||||
return 'Could not request GBAtemp: ' . $uri;
|
||||
}
|
||||
|
||||
$content = $html->find('div.messageContent, blockquote.baseHtml', 0)->innertext;
|
||||
$content = $html->find('article.message-body', 0)->innertext;
|
||||
return $this->cleanupPostContent($content, $site_url);
|
||||
}
|
||||
|
||||
@@ -80,12 +83,12 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
|
||||
switch($this->getInput('type')) {
|
||||
case 'N':
|
||||
foreach($html->find('li[class=news_item news full]') as $newsItem) {
|
||||
$url = self::URI . $newsItem->find('a', 0)->href;
|
||||
$img = $this->getURI() . extractFromDelimiters($newsItem->find('a.news_image', 0)->style, 'url(', ')') . '#.image';
|
||||
foreach($html->find('li.news_item.full') as $newsItem) {
|
||||
$url = urljoin(self::URI, $newsItem->find('a', 0)->href);
|
||||
$img = $this->findItemImage($newsItem, 'a.news_image');
|
||||
$time = $this->findItemDate($newsItem);
|
||||
$author = $newsItem->find('a.username', 0)->plaintext;
|
||||
$title = $newsItem->find('a', 1)->plaintext;
|
||||
$title = $this->decodeHtmlEntities($newsItem->find('h3.news_title', 0)->plaintext);
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
|
||||
unset($newsItem); // Some items are heavy, freeing the item proactively helps saving memory
|
||||
@@ -93,26 +96,23 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
break;
|
||||
case 'R':
|
||||
foreach($html->find('li.portal_review') as $reviewItem) {
|
||||
$url = self::URI . $reviewItem->find('a', 0)->href;
|
||||
$img = $this->getURI() . extractFromDelimiters($reviewItem->find('a', 0)->style, 'image:url(', ')');
|
||||
$title = $reviewItem->find('span.review_title', 0)->plaintext;
|
||||
$content = getSimpleHTMLDOM($url);
|
||||
$author = $content->find('a.username', 0)->plaintext;
|
||||
$url = urljoin(self::URI, $reviewItem->find('a.review_boxart', 0)->href);
|
||||
$img = $this->findItemImage($reviewItem, 'a.review_boxart');
|
||||
$title = $this->decodeHtmlEntities($reviewItem->find('h2.review_title', 0)->plaintext);
|
||||
$content = getSimpleHTMLDOMCached($url);
|
||||
$author = $content->find('span.author--name', 0)->plaintext;
|
||||
$time = $this->findItemDate($content);
|
||||
$intro = '<p><b>' . ($content->find('div#review_intro', 0)->plaintext) . '</b></p>';
|
||||
$intro = '<p><b>' . ($content->find('div#review_introduction', 0)->plaintext) . '</b></p>';
|
||||
$review = $content->find('div#review_main', 0)->innertext;
|
||||
$subheader = '<p><b>' . $content->find('div.review_subheader', 0)->plaintext . '</b></p>';
|
||||
$procons = $content->find('table.review_procons', 0)->outertext;
|
||||
$scores = $content->find('table.reviewscores', 0)->outertext;
|
||||
$content = $this->cleanupPostContent($intro . $review . $subheader . $procons . $scores, self::URI);
|
||||
$content = $this->cleanupPostContent($intro . $review, self::URI);
|
||||
$this->items[] = $this->buildItem($url, $title, $author, $time, $img, $content);
|
||||
unset($reviewItem); // Free up memory
|
||||
}
|
||||
break;
|
||||
case 'T':
|
||||
foreach($html->find('li.portal-tutorial') as $tutorialItem) {
|
||||
$url = self::URI . $tutorialItem->find('a', 1)->href;
|
||||
$title = $tutorialItem->find('a', 1)->plaintext;
|
||||
$url = urljoin(self::URI, $tutorialItem->find('a', 1)->href);
|
||||
$title = $this->decodeHtmlEntities($tutorialItem->find('a', 1)->plaintext);
|
||||
$time = $this->findItemDate($tutorialItem);
|
||||
$author = $tutorialItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
@@ -122,8 +122,8 @@ class GBAtempBridge extends BridgeAbstract {
|
||||
break;
|
||||
case 'F':
|
||||
foreach($html->find('li.rc_item') as $postItem) {
|
||||
$url = self::URI . $postItem->find('a', 1)->href;
|
||||
$title = $postItem->find('a', 1)->plaintext;
|
||||
$url = urljoin(self::URI, $postItem->find('a', 1)->href);
|
||||
$title = $this->decodeHtmlEntities($postItem->find('a', 1)->plaintext);
|
||||
$time = $this->findItemDate($postItem);
|
||||
$author = $postItem->find('a.username', 0)->plaintext;
|
||||
$content = $this->fetchPostContent($url, self::URI);
|
||||
|
@@ -32,6 +32,7 @@ class GQMagazineBridge extends BridgeAbstract
|
||||
'required' => true,
|
||||
'exampleValue' => 'sexe/news'
|
||||
),
|
||||
'limit' => self::LIMIT,
|
||||
));
|
||||
|
||||
const REPLACED_ATTRIBUTES = array(
|
||||
@@ -76,9 +77,12 @@ class GQMagazineBridge extends BridgeAbstract
|
||||
|
||||
// Since GQ don't want simple class scrapping, let's do it the hard way and ... discover content !
|
||||
$main = $html->find('main', 0);
|
||||
$limit = $this->getInput('limit') ?? 10;
|
||||
foreach ($main->find('a') as $link) {
|
||||
if(strpos($link, $this->getInput('page')))
|
||||
continue;
|
||||
if (count($this->items) >= $limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
$uri = $link->href;
|
||||
$date = $link->parent()->find('time', 0);
|
||||
|
||||
@@ -117,7 +121,7 @@ class GQMagazineBridge extends BridgeAbstract
|
||||
*/
|
||||
private function loadFullArticle($uri){
|
||||
$html = getSimpleHTMLDOMCached($uri);
|
||||
return $html->find('section[data-test-id=MainContentWrapper]', 0);
|
||||
return $html->find('article', 0);
|
||||
}
|
||||
|
||||
/**
|
||||
|
54
bridges/GatesNotesBridge.php
Normal file
54
bridges/GatesNotesBridge.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
class GatesNotesBridge extends FeedExpander {
|
||||
|
||||
const MAINTAINER = 'corenting';
|
||||
const NAME = 'Gates Notes';
|
||||
const URI = 'https://www.gatesnotes.com';
|
||||
const DESCRIPTION = 'Returns the newest articles.';
|
||||
const CACHE_TIMEOUT = 21600; // 6h
|
||||
|
||||
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_html = defaultLinkTo($article_html, $this->getURI());
|
||||
|
||||
$top_description = '<p>' . $article_html->find('div.article_top_description', 0)->innertext . '</p>';
|
||||
$hero_image = '<img src=' . $article_html->find('img.article_top_DMT_Image', 0)->getAttribute('data-src') . '>';
|
||||
|
||||
$article_body = $article_html->find('div.TGN_Article_ReadTimeSection', 0);
|
||||
// Convert iframe of Youtube videos to link
|
||||
foreach($article_body->find('iframe') as $found) {
|
||||
|
||||
$iframeUrl = $found->getAttribute('src');
|
||||
|
||||
if ($iframeUrl) {
|
||||
$text = 'Embedded Youtube video, click here to watch on Youtube.com';
|
||||
$found->outertext = '<p><a href="' . $iframeUrl . '">' . $text . '</a></p>';
|
||||
}
|
||||
}
|
||||
// Remove <link> CSS ressources
|
||||
foreach($article_body->find('link') as $found) {
|
||||
|
||||
$linkedRessourceUrl = $found->getAttribute('href');
|
||||
|
||||
if (str_ends_with($linkedRessourceUrl, '.css')) {
|
||||
$found->outertext = '';
|
||||
}
|
||||
}
|
||||
$article_body = sanitize($article_body->innertext);
|
||||
|
||||
$item['content'] = $top_description . $hero_image . $article_body;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData(){
|
||||
$feed = static::URI . '/rss';
|
||||
$this->collectExpandableDatas($feed);
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user