1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-16 13:34:11 +02:00

Compare commits

..

56 Commits

Author SHA1 Message Date
github-actions[bot]
6da40b8ec3 Upload pr2545-WordPress-pr-context1.html (d2ad2)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2545
Commit: d2ad22d3ef
2022-03-27 16:40:26 +00:00
github-actions[bot]
afc889a0e4 Upload pr2545-WordPress-current-context1.html (d2ad2)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2545
Commit: d2ad22d3ef
2022-03-27 16:40:25 +00:00
github-actions[bot]
5c684edf84 Upload pr2522-Phoronix-pr-context1.html (61b37)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2522
Commit: 61b37caeec
2022-03-27 11:19:11 +00:00
github-actions[bot]
0404be2d45 Upload pr2522-Phoronix-current-context1.html (61b37)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2522
Commit: 61b37caeec
2022-03-27 11:19:10 +00:00
github-actions[bot]
78e158cbcc Upload pr2545-WordPress-pr-context1.html (fed5b)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2545
Commit: fed5b01dbd
2022-03-27 01:54:42 +00:00
github-actions[bot]
2f1eade64b Upload pr2545-WordPress-current-context1.html (fed5b)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2545
Commit: fed5b01dbd
2022-03-27 01:54:42 +00:00
github-actions[bot]
cf82f9114e Upload pr2543-Arte7-pr-context2.html (f19f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2543
Commit: f19f5bab02
2022-03-26 18:36:04 +00:00
github-actions[bot]
5ae749f3b2 Upload pr2543-Arte7-pr-context1.html (f19f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2543
Commit: f19f5bab02
2022-03-26 18:36:03 +00:00
github-actions[bot]
029b20087d Upload pr2543-Arte7-current-context4.html (f19f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2543
Commit: f19f5bab02
2022-03-26 18:36:02 +00:00
github-actions[bot]
23c6f62d8b Upload pr2543-Arte7-current-context3.html (f19f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2543
Commit: f19f5bab02
2022-03-26 18:36:01 +00:00
github-actions[bot]
2768fd95f7 Upload pr2543-Arte7-current-context2.html (f19f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2543
Commit: f19f5bab02
2022-03-26 18:36:00 +00:00
github-actions[bot]
5932418f6d Upload pr2543-Arte7-current-context1.html (f19f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2543
Commit: f19f5bab02
2022-03-26 18:35:59 +00:00
github-actions[bot]
948eba546d Upload pr2541-FindACrew-pr-context1.html (babd8)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2541
Commit: babd82d3a4
2022-03-26 15:23:01 +00:00
github-actions[bot]
c06bb7d224 Upload pr2541-FindACrew-current-context1.html (babd8)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2541
Commit: babd82d3a4
2022-03-26 15:23:00 +00:00
github-actions[bot]
7a063e232b Upload pr2541-FindACrew-pr-context1.html (babd8)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2541
Commit: babd82d3a4
2022-03-26 15:18:28 +00:00
github-actions[bot]
368cfc4342 Upload pr2541-FindACrew-current-context1.html (babd8)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2541
Commit: babd82d3a4
2022-03-26 15:18:27 +00:00
github-actions[bot]
67ab97f4ac Upload pr2471-TwitterV2-pr-context3.html (e8319)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2471
Commit: e8319a98c2
2022-03-26 04:48:12 +00:00
github-actions[bot]
2e7c7bb5cd Upload pr2471-TwitterV2-pr-context2.html (e8319)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2471
Commit: e8319a98c2
2022-03-26 04:48:11 +00:00
github-actions[bot]
ef30655dd9 Upload pr2471-TwitterV2-pr-context1.html (e8319)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2471
Commit: e8319a98c2
2022-03-26 04:48:10 +00:00
github-actions[bot]
e94dbc471c Upload pr2471-TwitterV2-pr-context3.html (e665d)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2471
Commit: e665dc4ebc
2022-03-26 04:03:59 +00:00
github-actions[bot]
1ff5ad46a6 Upload pr2471-TwitterV2-pr-context2.html (e665d)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2471
Commit: e665dc4ebc
2022-03-26 04:03:58 +00:00
github-actions[bot]
327a82ded5 Upload pr2471-TwitterV2-pr-context1.html (e665d)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2471
Commit: e665dc4ebc
2022-03-26 04:03:57 +00:00
github-actions[bot]
9c377fad33 Upload pr1813-Unsplash-pr-context1.html (4a401)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/1813
Commit: 4a4013f967
2022-03-26 00:03:02 +00:00
github-actions[bot]
4de906c9a9 Upload pr1813-Unsplash-current-context1.html (4a401)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/1813
Commit: 4a4013f967
2022-03-26 00:03:02 +00:00
github-actions[bot]
79d077ccea Upload pr1813-Unsplash-pr-context1.html (58f05)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/1813
Commit: 58f05a0c99
2022-03-25 23:59:40 +00:00
github-actions[bot]
9efa93df2b Upload pr1813-Unsplash-current-context1.html (58f05)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/1813
Commit: 58f05a0c99
2022-03-25 23:59:40 +00:00
github-actions[bot]
82353fb3de Upload pr2451-GoogleGroups-pr-context1.html (3ba0d)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2451
Commit: 3ba0db1436
2022-03-25 23:56:31 +00:00
github-actions[bot]
4a0165b80e Upload pr2451-GoogleGroups-pr-context1.html (09d0a)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2451
Commit: 09d0abaa13
2022-03-25 23:19:14 +00:00
github-actions[bot]
2cedcadc2f Upload pr2451-GoogleGroups-pr-context1.html (85e65)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2451
Commit: 85e65a7aad
2022-03-25 22:44:04 +00:00
github-actions[bot]
64f511da32 Upload pr2048-TysolFr-pr-context1.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:42 +00:00
github-actions[bot]
3c34c2bea2 Upload pr2048-Scmb-pr-context1.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:41 +00:00
github-actions[bot]
bb578f5e9d Upload pr2048-Scmb-current-context1.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:40 +00:00
github-actions[bot]
4375821199 Upload pr2048-Rumble-pr-context4.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:40 +00:00
github-actions[bot]
653e82b0a9 Upload pr2048-Rumble-pr-context3.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:39 +00:00
github-actions[bot]
10ea0faf9e Upload pr2048-Rumble-pr-context2.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:39 +00:00
github-actions[bot]
b9e053c092 Upload pr2048-Rumble-pr-context1.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:38 +00:00
github-actions[bot]
55786ec33c Upload pr2048-HentaiHaven-pr-context1.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:37 +00:00
github-actions[bot]
1cd346dd18 Upload pr2048-HentaiHaven-current-context1.html (f1b92)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2048
Commit: f1b9265718
2022-03-25 21:52:37 +00:00
github-actions[bot]
e46b27dead Upload pr2476-EZTV-pr-context1.html (db8f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2476
Commit: db8f5a4a55
2022-03-25 21:19:59 +00:00
github-actions[bot]
05bfcfd347 Upload pr2476-EZTV-current-context1.html (db8f5)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2476
Commit: db8f5a4a55
2022-03-25 21:19:58 +00:00
github-actions[bot]
4a22681db2 Upload pr2476-EZTV-pr-context1.html (293b4)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2476
Commit: 293b4f293a
2022-03-25 20:10:23 +00:00
github-actions[bot]
9d28189c61 Upload pr2476-EZTV-current-context1.html (293b4)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2476
Commit: 293b4f293a
2022-03-25 20:10:23 +00:00
github-actions[bot]
876bd4984c Upload pr1594-YouTubeCommunityTab-pr-context2.html (ae4a1)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/1594
Commit: ae4a14ee35
2022-03-25 19:50:45 +00:00
github-actions[bot]
1c9d74925b Upload pr1594-YouTubeCommunityTab-pr-context1.html (ae4a1)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/1594
Commit: ae4a14ee35
2022-03-25 19:50:44 +00:00
github-actions[bot]
d861714fee Upload pr2535-DockerHub-pr-context2.html (fdddd)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: fdddd095ef
2022-03-25 18:57:50 +00:00
github-actions[bot]
184c997b08 Upload pr2535-DockerHub-pr-context1.html (fdddd)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: fdddd095ef
2022-03-25 18:57:49 +00:00
github-actions[bot]
31d6262f1e Upload pr2535-DockerHub-current-context2.html (fdddd)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: fdddd095ef
2022-03-25 18:57:48 +00:00
github-actions[bot]
1302560ba2 Upload pr2535-DockerHub-current-context1.html (fdddd)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: fdddd095ef
2022-03-25 18:57:47 +00:00
github-actions[bot]
f63803f8a2 Upload pr2535-DockerHub-pr-context2.html (228b1)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 228b1ee299
2022-03-25 18:40:25 +00:00
github-actions[bot]
e584e82178 Upload pr2535-DockerHub-pr-context1.html (228b1)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 228b1ee299
2022-03-25 18:40:24 +00:00
github-actions[bot]
504c31a8af Upload pr2535-DockerHub-current-context2.html (228b1)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 228b1ee299
2022-03-25 18:40:23 +00:00
github-actions[bot]
dd8b816085 Upload pr2535-DockerHub-current-context1.html (228b1)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 228b1ee299
2022-03-25 18:40:23 +00:00
github-actions[bot]
225b2ab142 Upload pr2535-DockerHub-pr-context2.html (59081)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 59081ff585
2022-03-25 16:07:54 +00:00
github-actions[bot]
e6ef24021a Upload pr2535-DockerHub-pr-context1.html (59081)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 59081ff585
2022-03-25 16:07:53 +00:00
github-actions[bot]
baf094e12e Upload pr2535-DockerHub-current-context2.html (59081)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 59081ff585
2022-03-25 16:07:53 +00:00
github-actions[bot]
2d1d6473f2 Upload pr2535-DockerHub-current-context1.html (59081)
Pull request: https://github.com/RSS-Bridge/rss-bridge/pull/2535
Commit: 59081ff585
2022-03-25 16:07:52 +00:00
637 changed files with 70695 additions and 70495 deletions

View File

@@ -1,8 +0,0 @@
FROM rssbridge/rss-bridge:latest
RUN apt-get update && \
apt-get install --yes --no-install-recommends \
git && \
pecl install xdebug && \
pear install PHP_CodeSniffer && \
docker-php-ext-enable xdebug

View File

@@ -1,27 +0,0 @@
{
"name": "rss-bridge dev",
"build": { "dockerfile": "Dockerfile" },
"customizations": {
// Configure properties specific to VS Code.
"vscode": {
// Set *default* container specific settings.json values on container create.
"settings": {
"php.validate.executablePath": "/usr/local/bin/php",
"phpSniffer.executablesFolder": "/usr/local/bin/",
"phpcs.executablePath": "/usr/local/bin/phpcs",
"phpcs.lintOnType": false
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"xdebug.php-debug",
"bmewburn.vscode-intelephense-client",
"philfontaine.autolaunch",
"eamodio.gitlens",
"shevaua.phpcs"
]
}
},
"forwardPorts": [3100, 9000, 9003],
"postCreateCommand": "cp .devcontainer/nginx.conf /etc/nginx/conf.d/default.conf && cp .devcontainer/xdebug.ini /usr/local/etc/php/conf.d/xdebug.ini && mkdir .vscode && cp .devcontainer/launch.json .vscode && echo '*' > whitelist.txt && chmod a+x \"$(pwd)\" && rm -rf /var/www/html && ln -s \"$(pwd)\" /var/www/html && nginx && php-fpm -D"
}

View File

@@ -1,49 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Listen for Xdebug",
"type": "php",
"request": "launch",
"port": 9003,
"auto": true
},
{
"name": "Launch currently open script",
"type": "php",
"request": "launch",
"program": "${file}",
"cwd": "${fileDirname}",
"port": 0,
"runtimeArgs": [
"-dxdebug.start_with_request=yes"
],
"env": {
"XDEBUG_MODE": "debug,develop",
"XDEBUG_CONFIG": "client_port=${port}"
}
},
{
"name": "Launch Built-in web server",
"type": "php",
"request": "launch",
"runtimeArgs": [
"-dxdebug.mode=debug",
"-dxdebug.start_with_request=yes",
"-S",
"localhost:0"
],
"program": "",
"cwd": "${workspaceRoot}",
"port": 9003,
"serverReadyAction": {
"pattern": "Development Server \\(http://localhost:([0-9]+)\\) started",
"uriFormat": "http://localhost:%s",
"action": "openExternally"
}
}
]
}

View File

@@ -1,17 +0,0 @@
server {
listen 3100 default_server;
root /workspaces/rss-bridge;
access_log /var/log/nginx/rssbridge.access.log;
error_log /var/log/nginx/rssbridge.error.log;
index index.php;
location ~ /(\.|vendor|tests) {
deny all;
return 403; # Forbidden
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass 127.0.0.1:9000;
}
}

View File

@@ -1,7 +0,0 @@
[xdebug]
xdebug.mode=develop,debug
xdebug.client_host=localhost
xdebug.client_port=9003
xdebug.start_with_request=yes
xdebug.discover_client_host=false
xdebug.log='/var/www/html/xdebug.log'

View File

@@ -1,4 +0,0 @@
# Reformat code base to PSR12
4f75591060d95208a301bc6bf460d875631b29cc
# Fix coding style missed by phpbcf
951092eef374db048b77bac85e75e3547bfac702

1
.gitattributes vendored
View File

@@ -1,6 +1,5 @@
# Auto detect text files and perform LF normalization
* text=auto
*.sh text eol=lf
# Custom for Visual Studio
*.cs diff=csharp

View File

@@ -1,7 +1,49 @@
### Pull request policy
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.
* [Fix one issue per pull request](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#fix-one-issue-per-pull-request)
* [Respect the coding style policy](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#respect-the-coding-style-policy)
* [Properly name your commits](https://github.com/RSS-Bridge/rss-bridge/wiki/Pull-request-policy#properly-name-your-commits)
* When fixing a bridge (located in the `bridges` directory), write `[BridgeName] Feature` <br>(i.e. `[YoutubeBridge] Fix typo in video titles`).
* When fixing other files, use `[FileName] Feature` <br>(i.e. `[index.php] Add multilingual support`).
* When fixing a general problem that applies to multiple files, write `category: feature` <br>(i.e. `bridges: Fix various typos`).
Note that all pull-requests must pass all tests before they can be merged.
### Coding style
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.
* [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)

View File

@@ -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 Documentation](https://rss-bridge.github.io/rss-bridge/For_Developers/index.html) developer section.
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.
-->

View File

@@ -1,2 +0,0 @@
beautifulsoup4>=4.10.0
requests>=2.26.0

113
.github/prtester.py vendored
View File

@@ -1,113 +0,0 @@
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
print(bridgeid + "\n")
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')
cleanlist = []
for option in listing.contents:
if 'optgroup' in option.name:
cleanlist.extend(option)
else:
cleanlist.append(option)
firstselectionentry = 1
for selectionentry in cleanlist:
if firstselectionentry:
selectionvalue = selectionentry.get('value')
firstselectionentry = 0
else:
if 'selected' in selectionentry.attrs:
selectionvalue = selectionentry.get('value')
break
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/style.css','https://rss-bridge.org/bridge01/static/style.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

View File

@@ -17,11 +17,11 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v3
uses: actions/checkout@v2.3.4
-
name: Docker meta
id: docker_meta
uses: docker/metadata-action@v4
uses: docker/metadata-action@v3.5.0
with:
images: |
${{ env.DOCKERHUB_SLUG }}
@@ -33,26 +33,26 @@ jobs:
type=raw,value=stable,enable=${{ startsWith(github.ref, 'refs/tags/20') }}
-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
uses: docker/setup-qemu-action@v1
-
name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
uses: docker/setup-buildx-action@v1.6.0
-
name: Login to DockerHub
uses: docker/login-action@v2
uses: docker/login-action@v1.10.0
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
-
name: Login to GitHub Container Registry
uses: docker/login-action@v2
uses: docker/login-action@v1.10.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Build and push
uses: docker/bake-action@v2
uses: docker/bake-action@v1.6.0
with:
files: |
./docker-bake.hcl

View File

@@ -9,11 +9,11 @@ jobs:
documentation:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
with:
persist-credentials: false
- name: Setup PHP
uses: shivammathur/setup-php@v2
uses: shivammathur/setup-php@2.17.1
with:
php-version: 8.0
- name: Install dependencies
@@ -21,7 +21,7 @@ jobs:
- name: Generate documentation
run: daux generate
- name: Deploy same repository 🚀
uses: JamesIves/github-pages-deploy-action@v4
uses: JamesIves/github-pages-deploy-action@v4.2.5
with:
folder: "static"
branch: gh-pages
branch: gh-pages

View File

@@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-versions: ['7.4']
php-versions: ['7.1', '7.2', '7.3', '7.4']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
@@ -24,13 +24,12 @@ jobs:
runs-on: ubuntu-20.04
strategy:
matrix:
php-versions: ['7.4']
php-versions: ['7.1', '7.4']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global config --no-plugins allow-plugins.dealerdirect/phpcodesniffer-composer-installer true
- 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
@@ -38,7 +37,7 @@ jobs:
executable_php_files_check:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- run: |
if find -name "*.php" -executable -type f -print -exec false {} +
then

View File

@@ -1,67 +0,0 @@
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@v3
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@v4
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 "bodylength=${#body}" >> $GITHUB_OUTPUT
- 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-file: comment.txt
edit-mode: replace

View File

@@ -7,15 +7,28 @@ on:
branches: [ master ]
jobs:
phpunit7:
runs-on: ubuntu-20.04
strategy:
matrix:
php-versions: ['7.1', '7.2', '7.3']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^7
- run: phpunit --configuration=phpunit.xml --include-path=lib/
phpunit8:
runs-on: ubuntu-20.04
strategy:
matrix:
php-versions: ['7.4', '8.0', '8.1']
php-versions: ['7.3', '7.4', '8.0', '8.1']
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer install
- run: composer test
- run: composer global require phpunit/phpunit ^8
- run: phpunit --configuration=phpunit.xml --include-path=lib/

1
.gitignore vendored
View File

@@ -229,7 +229,6 @@ pip-log.txt
/whitelist.txt
DEBUG
config.ini.php
config/*
######################
## VisualStudioCode ##

View File

@@ -1,223 +0,0 @@
# Contributors
* [16mhz](https://github.com/16mhz)
* [adamchainz](https://github.com/adamchainz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [akirk](https://github.com/akirk)
* [Albirew](https://github.com/Albirew)
* [aledeg](https://github.com/aledeg)
* [alex73](https://github.com/alex73)
* [alexAubin](https://github.com/alexAubin)
* [Alkarex](https://github.com/Alkarex)
* [AmauryCarrade](https://github.com/AmauryCarrade)
* [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)
* [Chouchen](https://github.com/Chouchen)
* [ckiw](https://github.com/ckiw)
* [cn-tools](https://github.com/cn-tools)
* [cnlpete](https://github.com/cnlpete)
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [csisoap](https://github.com/csisoap)
* [da2x](https://github.com/da2x)
* [dabenzel](https://github.com/dabenzel)
* [Daiyousei](https://github.com/Daiyousei)
* [dawidsowa](https://github.com/dawidsowa)
* [DevonHess](https://github.com/DevonHess)
* [dhuschde](https://github.com/dhuschde)
* [disk0x](https://github.com/disk0x)
* [DJCrashdummy](https://github.com/DJCrashdummy)
* [Djuuu](https://github.com/Djuuu)
* [DnAp](https://github.com/DnAp)
* [dominik-th](https://github.com/dominik-th)
* [Draeli](https://github.com/Draeli)
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
* [drego85](https://github.com/drego85)
* [drklee3](https://github.com/drklee3)
* [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)
* [f0086](https://github.com/f0086)
* [fanch317](https://github.com/fanch317)
* [fatuuse](https://github.com/fatuuse)
* [fivefilters](https://github.com/fivefilters)
* [floviolleau](https://github.com/floviolleau)
* [fluffy-critter](https://github.com/fluffy-critter)
* [fmachen](https://github.com/fmachen)
* [Frenzie](https://github.com/Frenzie)
* [fulmeek](https://github.com/fulmeek)
* [ggiessen](https://github.com/ggiessen)
* [gileri](https://github.com/gileri)
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
* [girlpunk](https://github.com/girlpunk)
* [Glandos](https://github.com/Glandos)
* [gloony](https://github.com/gloony)
* [GregThib](https://github.com/GregThib)
* [griffaurel](https://github.com/griffaurel)
* [Grummfy](https://github.com/Grummfy)
* [gsantner](https://github.com/gsantner)
* [guigot](https://github.com/guigot)
* [hollowleviathan](https://github.com/hollowleviathan)
* [hpacleb](https://github.com/hpacleb)
* [hunhejj](https://github.com/hunhejj)
* [husim0](https://github.com/husim0)
* [IceWreck](https://github.com/IceWreck)
* [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)
* [jdigilio](https://github.com/jdigilio)
* [JeremyRand](https://github.com/JeremyRand)
* [JimDog546](https://github.com/JimDog546)
* [jNullj](https://github.com/jNullj)
* [Jocker666z](https://github.com/Jocker666z)
* [johnnygroovy](https://github.com/johnnygroovy)
* [johnpc](https://github.com/johnpc)
* [joni1993](https://github.com/joni1993)
* [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)
* [liamka](https://github.com/liamka)
* [Limero](https://github.com/Limero)
* [LogMANOriginal](https://github.com/LogMANOriginal)
* [lorenzos](https://github.com/lorenzos)
* [lukasklinger](https://github.com/lukasklinger)
* [m0zes](https://github.com/m0zes)
* [Mar-Koeh](https://github.com/Mar-Koeh)
* [marcus-at-localhost](https://github.com/marcus-at-localhost)
* [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)
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [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)
* [ORelio](https://github.com/ORelio)
* [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)
* [Riduidel](https://github.com/Riduidel)
* [rogerdc](https://github.com/rogerdc)
* [Roliga](https://github.com/Roliga)
* [ronansalmon](https://github.com/ronansalmon)
* [rremizov](https://github.com/rremizov)
* [s0lesurviv0r](https://github.com/s0lesurviv0r)
* [sal0max](https://github.com/sal0max)
* [sebsauvage](https://github.com/sebsauvage)
* [shutosg](https://github.com/shutosg)
* [simon816](https://github.com/simon816)
* [Simounet](https://github.com/Simounet)
* [somini](https://github.com/somini)
* [SpangleLabs](https://github.com/SpangleLabs)
* [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)
* [sublimz](https://github.com/sublimz)
* [sunchaserinfo](https://github.com/sunchaserinfo)
* [SuperSandro2000](https://github.com/SuperSandro2000)
* [sysadminstory](https://github.com/sysadminstory)
* [t0stiman](https://github.com/t0stiman)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [tgkenney](https://github.com/tgkenney)
* [thefranke](https://github.com/thefranke)
* [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)
* [yamanq](https://github.com/yamanq)
* [yardenac](https://github.com/yardenac)
* [ymeister](https://github.com/ymeister)
* [yue-dongchen](https://github.com/yue-dongchen)
* [ZeNairolf](https://github.com/ZeNairolf)

View File

@@ -1,36 +1,24 @@
FROM lwthiker/curl-impersonate:0.5-ff-slim-buster AS curlimpersonate
FROM php:8.0.27-fpm-buster AS rssbridge
FROM php:7-apache-buster
LABEL description="RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one."
LABEL repository="https://github.com/RSS-Bridge/rss-bridge"
LABEL website="https://github.com/RSS-Bridge/rss-bridge"
RUN apt-get update && \
apt-get install --yes --no-install-recommends \
nginx \
zlib1g-dev \
libzip-dev \
libmemcached-dev \
nss-plugin-pem \
libicu-dev && \
docker-php-ext-install zip && \
docker-php-ext-install intl && \
pecl install memcached && \
docker-php-ext-enable memcached && \
docker-php-ext-enable opcache && \
mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
ENV APACHE_DOCUMENT_ROOT=/app
COPY ./config/nginx.conf /etc/nginx/sites-enabled/default
RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& apt-get --yes update \
&& apt-get --yes --no-install-recommends install \
zlib1g-dev \
libmemcached-dev \
&& rm -rf /var/lib/apt/lists/* \
&& pecl install memcached \
&& docker-php-ext-enable memcached \
&& sed -ri -e 's!/var/www/html!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/sites-available/*.conf \
&& sed -ri -e 's!/var/www/!${APACHE_DOCUMENT_ROOT}!g' /etc/apache2/apache2.conf /etc/apache2/conf-available/*.conf \
&& sed -ri -e 's/(MinProtocol\s*=\s*)TLSv1\.2/\1None/' /etc/ssl/openssl.cnf \
&& sed -ri -e 's/(CipherString\s*=\s*DEFAULT)@SECLEVEL=2/\1/' /etc/ssl/openssl.cnf
COPY --chown=www-data:www-data ./ /app/
COPY --from=curlimpersonate /usr/local/lib/libcurl-impersonate-ff.so /usr/local/lib/curl-impersonate/
ENV LD_PRELOAD /usr/local/lib/curl-impersonate/libcurl-impersonate-ff.so
ENV CURL_IMPERSONATE ff91esr
EXPOSE 80
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["/app/docker-entrypoint.sh"]

537
README.md
View File

@@ -1,242 +1,42 @@
# RSS-Bridge
![RSS-Bridge](static/logo_600px.png)
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one.
===
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE)
[![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest)
[![irc.libera.chat](https://img.shields.io/badge/irc.libera.chat-%23rssbridge-blue.svg)](https://web.libera.chat/#rssbridge)
[![Chat on Matrix](https://matrix.to/img/matrix-badge.svg)](https://matrix.to/#/#rssbridge:libera.chat)
[![Actions Status](https://img.shields.io/github/actions/workflow/status/RSS-Bridge/rss-bridge/tests.yml?branch=master&label=GitHub%20Actions&logo=github)](https://github.com/RSS-Bridge/rss-bridge/actions)
[![Actions Status](https://img.shields.io/github/workflow/status/RSS-Bridge/rss-bridge/Tests/master?label=GitHub%20Actions&logo=github)](https://github.com/RSS-Bridge/rss-bridge/actions)
[![Docker Build Status](https://img.shields.io/docker/cloud/build/rssbridge/rss-bridge?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
|||
|:-:|:-:|
|![Screenshot #1](/static/screenshot-1.png?raw=true)|![Screenshot #2](/static/screenshot-2.png?raw=true)|
|![Screenshot #3](/static/screenshot-3.png?raw=true)|![Screenshot #4](/static/screenshot-4.png?raw=true)|
|![Screenshot #5](/static/screenshot-5.png?raw=true)|![Screenshot #6](/static/screenshot-6.png?raw=true)|
|![Screenshot #7](/static/twitter-form.png?raw=true)|![Screenshot #8](/static/twitter-rasmus.png?raw=true)|
RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites that don't have one. It can be used on webservers or as a stand-alone application in CLI mode.
## A subset of bridges
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
* `YouTube` : YouTube user channel, playlist or search
* `Twitter` : Return keyword/hashtag search or user timeline
* `Telegram` : Return the latest posts from a public group
* `Reddit` : Return the latest posts from a subreddit or user
* `Filter` : Filter an existing feed url
* `Vk` : Latest posts from a user or group
* `FeedMerge` : Merge two or more existing feeds into one
* `Twitch` : Fetch the latest videos from a channel
Supported sites/pages (examples)
===
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/) (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
* `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/)
* `Pinterest`: Most recent photos from user or search
* `ScmbBridge`: Newest stories from [secouchermoinsbete.fr](http://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
* `YouTube` : YouTube user channel, playlist or search
And [many more](bridges/), thanks to the community!
[Full documentation](https://rss-bridge.github.io/rss-bridge/index.html)
Output format
===
Check out RSS-Bridge right now on https://rss-bridge.org/bridge01 or find another
[public instance](https://rss-bridge.github.io/rss-bridge/General/Public_Hosts.html).
## Tutorial
RSS-Bridge requires php 7.4 (or higher).
### Install with composer or git
```shell
cd /var/www
composer create-project --no-dev rss-bridge/rss-bridge
```
```shell
cd /var/www
git clone https://github.com/RSS-Bridge/rss-bridge.git
```
Config:
```shell
# Give the http user write permission to the cache folder
chown www-data:www-data /var/www/rss-bridge/cache
# Optionally copy over the default config file
cp config.default.ini.php config.ini.php
```
Example config for nginx:
```nginx
# /etc/nginx/sites-enabled/rssbridge
server {
listen 80;
server_name example.com;
root /var/www/rss-bridge;
index index.php;
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_read_timeout 60s;
fastcgi_pass unix:/run/php/php-fpm.sock;
}
}
```
### Install with Docker:
Install by using docker image from Docker Hub:
```bash
# Create container
docker create --name=rss-bridge --publish 3000:80 rssbridge/rss-bridge
# Start container
docker start rss-bridge
```
Browse http://localhost:3000/
Install by locally building the image:
```bash
# Build image from Dockerfile
docker build -t rss-bridge .
# Create container
docker create --name rss-bridge --publish 3000:80 rss-bridge
# Start the container
docker start rss-bridge
```
Browse http://localhost:3000/
#### Install with docker-compose
Create a `docker-compose.yml` file locally with with the following content:
```yml
version: '2'
services:
rss-bridge:
image: rssbridge/rss-bridge:latest
volumes:
- </local/custom/path>:/config
ports:
- 3000:80
restart: unless-stopped
```
Then launch with `docker-compose`:
```bash
docker-compose up
```
Browse http://localhost:3000/
### Alternative installation methods
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
[![Deploy to Cloudron](https://cloudron.io/img/button.svg)](https://www.cloudron.io/store/com.rssbridgeapp.cloudronapp.html)
The Heroku quick deploy currently does not work. It might possibly work if you fork this repo and
modify the `repository` in `scalingo.json`. See https://github.com/RSS-Bridge/rss-bridge/issues/2688
Learn more in
[Installation](https://rss-bridge.github.io/rss-bridge/For_Hosts/Installation.html).
## How-to
### How to create a new bridge from scratch
Create the new bridge in e.g. `bridges/BearBlogBridge.php`:
```php
<?php
class BearBlogBridge extends BridgeAbstract
{
const NAME = 'BearBlog (bearblog.dev)';
public function collectData()
{
$dom = getSimpleHTMLDOM('https://herman.bearblog.dev/blog/');
foreach ($dom->find('.blog-posts li') as $li) {
$a = $li->find('a', 0);
$this->items[] = [
'title' => $a->plaintext,
'uri' => 'https://herman.bearblog.dev' . $a->href,
];
}
}
}
```
Learn more in [bridge api](https://rss-bridge.github.io/rss-bridge/Bridge_API/index.html).
### How to enable all bridges
enabled_bridges[] = *
### How to enable some bridges
```
enabled_bridges[] = TwitchBridge
enabled_bridges[] = GettrBridge
```
### How to enable debug mode
enable_debug_mode = true
Learn more in [debug mode](https://rss-bridge.github.io/rss-bridge/For_Developers/Debug_mode.html).
### How to create a new output format
[Create a new format](https://rss-bridge.github.io/rss-bridge/Format_API/index.html).
## Explanation
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).
See [CONTRIBUTORS.md](CONTRIBUTORS.md)
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.
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!
## Reference
### FeedItem properties
```php
$item = [
'uri' => 'https://example.com/blog/hello',
'title' => 'Hello world',
// Publication date in unix timestamp
'timestamp' => 1668706254,
'author' => 'Alice',
'content' => 'Here be item content',
'enclosures' => [
'https://example.com/foo.png',
'https://example.com/bar.png'
],
'categories' => [
'news',
'tech',
],
// Globally unique id
'uid' => 'e7147580c8747aad',
]
```
### Output formats:
RSS-Bridge is capable of producing several output formats:
* `Atom` : Atom feed, for use in feed readers
* `Html` : Simple HTML page
@@ -244,18 +44,291 @@ That way you can host your own RSS-Bridge service with your favorite collection
* `Mrss` : MRSS feed, for use in feed readers
* `Plaintext` : Raw text, for consumption by other applications
### Licenses
You can extend RSS-Bridge with your own format, using the [Format API](https://github.com/RSS-Bridge/rss-bridge/wiki/Format-API)!
Screenshot
===
Welcome screen:
![Screenshot](https://github.com/RSS-Bridge/rss-bridge/wiki/images/screenshot_rss-bridge_welcome.png)
***
RSS-Bridge hashtag (#rss-bridge) search on Twitter, in Atom format (as displayed by Firefox):
![Screenshot](https://github.com/RSS-Bridge/rss-bridge/wiki/images/screenshot_twitterbridge_atom.png)
Requirements
===
RSS-Bridge requires PHP 7.1 or higher with following extensions enabled:
- [`openssl`](https://secure.php.net/manual/en/book.openssl.php)
- [`libxml`](https://secure.php.net/manual/en/book.libxml.php)
- [`mbstring`](https://secure.php.net/manual/en/book.mbstring.php)
- [`simplexml`](https://secure.php.net/manual/en/book.simplexml.php)
- [`curl`](https://secure.php.net/manual/en/book.curl.php)
- [`json`](https://secure.php.net/manual/en/book.json.php)
- [`filter`](https://secure.php.net/manual/en/book.filter.php)
- [`sqlite3`](http://php.net/manual/en/book.sqlite3.php) (only when using SQLiteCache)
Find more information on our [Wiki](https://github.com/rss-bridge/rss-bridge/wiki)
Enable / Disable bridges
===
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)
**Notice**: By default, RSS-Bridge will only show a small subset of bridges. Make sure to read up on [whitelisting](https://github.com/RSS-Bridge/rss-bridge/wiki/Whitelisting) to unlock the full potential of RSS-Bridge!
Deploy
===
Thanks to the community, hosting your own instance of RSS-Bridge is as easy as clicking a button!
[![Deploy on Scalingo](https://cdn.scalingo.com/deploy/button.svg)](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
[![Deploy to Heroku](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
Getting involved
===
There are many ways for you to getting involved with RSS-Bridge. Here are a few things:
- Share RSS-Bridge with your friends (Twitter, Facebook, ..._you name it_...)
- Report broken bridges or bugs by opening [Issues](https://github.com/RSS-Bridge/rss-bridge/issues) on GitHub
- Request new features or suggest ideas (via [Issues](https://github.com/RSS-Bridge/rss-bridge/issues))
- Discuss bugs, features, ideas or [issues](https://github.com/RSS-Bridge/rss-bridge/issues)
- Add new bridges or improve the API
- Improve the [Wiki](https://github.com/RSS-Bridge/rss-bridge/wiki)
- Host an instance of RSS-Bridge for your personal use or make it available to the community :sparkling_heart:
Authors
===
We are RSS-Bridge community, a group of developers continuing the project initiated by sebsauvage, webmaster of [sebsauvage.net](http://sebsauvage.net), author of [Shaarli](http://sebsauvage.net/wiki/doku.php?id=php:shaarli) and [ZeroBin](http://sebsauvage.net/wiki/doku.php?id=php:zerobin).
**Contributors** (sorted alphabetically):
<!--
Use this script to generate the list automatically (using the GitHub API):
./contrib/prepare_release/fetch_contributors.php
-->
* [16mhz](https://github.com/16mhz)
* [adamchainz](https://github.com/adamchainz)
* [Ahiles3005](https://github.com/Ahiles3005)
* [akirk](https://github.com/akirk)
* [Albirew](https://github.com/Albirew)
* [aledeg](https://github.com/aledeg)
* [alex73](https://github.com/alex73)
* [alexAubin](https://github.com/alexAubin)
* [AmauryCarrade](https://github.com/AmauryCarrade)
* [AntoineTurmel](https://github.com/AntoineTurmel)
* [arnd-s](https://github.com/arnd-s)
* [ArthurHoaro](https://github.com/ArthurHoaro)
* [Astalaseven](https://github.com/Astalaseven)
* [Astyan-42](https://github.com/Astyan-42)
* [AxorPL](https://github.com/AxorPL)
* [ayacoo](https://github.com/ayacoo)
* [az5he6ch](https://github.com/az5he6ch)
* [b1nj](https://github.com/b1nj)
* [benasse](https://github.com/benasse)
* [Binnette](https://github.com/Binnette)
* [Bockiii](https://github.com/Bockiii)
* [captn3m0](https://github.com/captn3m0)
* [chemel](https://github.com/chemel)
* [Chouchen](https://github.com/Chouchen)
* [ckiw](https://github.com/ckiw)
* [cn-tools](https://github.com/cn-tools)
* [cnlpete](https://github.com/cnlpete)
* [corenting](https://github.com/corenting)
* [couraudt](https://github.com/couraudt)
* [csisoap](https://github.com/csisoap)
* [da2x](https://github.com/da2x)
* [dabenzel](https://github.com/dabenzel)
* [Daiyousei](https://github.com/Daiyousei)
* [dawidsowa](https://github.com/dawidsowa)
* [DevonHess](https://github.com/DevonHess)
* [dhuschde](https://github.com/dhuschde)
* [disk0x](https://github.com/disk0x)
* [DJCrashdummy](https://github.com/DJCrashdummy)
* [Djuuu](https://github.com/Djuuu)
* [DnAp](https://github.com/DnAp)
* [dominik-th](https://github.com/dominik-th)
* [Draeli](https://github.com/Draeli)
* [Dreckiger-Dan](https://github.com/Dreckiger-Dan)
* [drego85](https://github.com/drego85)
* [drklee3](https://github.com/drklee3)
* [dvikan](https://github.com/dvikan)
* [em92](https://github.com/em92)
* [eMerzh](https://github.com/eMerzh)
* [EtienneM](https://github.com/EtienneM)
* [f0086](https://github.com/f0086)
* [fanch317](https://github.com/fanch317)
* [fatuuse](https://github.com/fatuuse)
* [fivefilters](https://github.com/fivefilters)
* [floviolleau](https://github.com/floviolleau)
* [fluffy-critter](https://github.com/fluffy-critter)
* [fmachen](https://github.com/fmachen)
* [Frenzie](https://github.com/Frenzie)
* [fulmeek](https://github.com/fulmeek)
* [ggiessen](https://github.com/ggiessen)
* [Ginko-Aloe](https://github.com/Ginko-Aloe)
* [girlpunk](https://github.com/girlpunk)
* [Glandos](https://github.com/Glandos)
* [gloony](https://github.com/gloony)
* [GregThib](https://github.com/GregThib)
* [griffaurel](https://github.com/griffaurel)
* [Grummfy](https://github.com/Grummfy)
* [gsantner](https://github.com/gsantner)
* [guigot](https://github.com/guigot)
* [hollowleviathan](https://github.com/hollowleviathan)
* [hpacleb](https://github.com/hpacleb)
* [hunhejj](https://github.com/hunhejj)
* [husim0](https://github.com/husim0)
* [IceWreck](https://github.com/IceWreck)
* [imagoiq](https://github.com/imagoiq)
* [j0k3r](https://github.com/j0k3r)
* [JackNUMBER](https://github.com/JackNUMBER)
* [jacquesh](https://github.com/jacquesh)
* [JasonGhent](https://github.com/JasonGhent)
* [jcgoette](https://github.com/jcgoette)
* [jdesgats](https://github.com/jdesgats)
* [jdigilio](https://github.com/jdigilio)
* [JeremyRand](https://github.com/JeremyRand)
* [JimDog546](https://github.com/JimDog546)
* [jNullj](https://github.com/jNullj)
* [Jocker666z](https://github.com/Jocker666z)
* [johnnygroovy](https://github.com/johnnygroovy)
* [johnpc](https://github.com/johnpc)
* [joni1993](https://github.com/joni1993)
* [KamaleiZestri](https://github.com/KamaleiZestri)
* [klimplant](https://github.com/klimplant)
* [kolarcz](https://github.com/kolarcz)
* [kranack](https://github.com/kranack)
* [kraoc](https://github.com/kraoc)
* [l1n](https://github.com/l1n)
* [laBecasse](https://github.com/laBecasse)
* [lagaisse](https://github.com/lagaisse)
* [lalannev](https://github.com/lalannev)
* [ldidry](https://github.com/ldidry)
* [Leomaradan](https://github.com/Leomaradan)
* [leyrer](https://github.com/leyrer)
* [liamka](https://github.com/liamka)
* [Limero](https://github.com/Limero)
* [LogMANOriginal](https://github.com/LogMANOriginal)
* [lorenzos](https://github.com/lorenzos)
* [lukasklinger](https://github.com/lukasklinger)
* [m0zes](https://github.com/m0zes)
* [Mar-Koeh](https://github.com/Mar-Koeh)
* [marcus-at-localhost](https://github.com/marcus-at-localhost)
* [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)
* [mightymt](https://github.com/mightymt)
* [mitsukarenai](https://github.com/mitsukarenai)
* [Monocularity](https://github.com/Monocularity)
* [MonsieurPoutounours](https://github.com/MonsieurPoutounours)
* [mr-flibble](https://github.com/mr-flibble)
* [mro](https://github.com/mro)
* [mschwld](https://github.com/mschwld)
* [mxmehl](https://github.com/mxmehl)
* [Mynacol](https://github.com/Mynacol)
* [nel50n](https://github.com/nel50n)
* [niawag](https://github.com/niawag)
* [Niehztog](https://github.com/Niehztog)
* [Nono-m0le](https://github.com/Nono-m0le)
* [obsiwitch](https://github.com/obsiwitch)
* [Ololbu](https://github.com/Ololbu)
* [ORelio](https://github.com/ORelio)
* [otakuf](https://github.com/otakuf)
* [Park0](https://github.com/Park0)
* [Paroleen](https://github.com/Paroleen)
* [PaulVayssiere](https://github.com/PaulVayssiere)
* [pellaeon](https://github.com/pellaeon)
* [PeterDaveHello](https://github.com/PeterDaveHello)
* [Peterr-K](https://github.com/Peterr-K)
* [Piranhaplant](https://github.com/Piranhaplant)
* [pit-fgfjiudghdf](https://github.com/pit-fgfjiudghdf)
* [pitchoule](https://github.com/pitchoule)
* [pmaziere](https://github.com/pmaziere)
* [Pofilo](https://github.com/Pofilo)
* [prysme01](https://github.com/prysme01)
* [Qluxzz](https://github.com/Qluxzz)
* [quentinus95](https://github.com/quentinus95)
* [rakoo](https://github.com/rakoo)
* [RawkBob](https://github.com/RawkBob)
* [regisenguehard](https://github.com/regisenguehard)
* [Riduidel](https://github.com/Riduidel)
* [rogerdc](https://github.com/rogerdc)
* [Roliga](https://github.com/Roliga)
* [ronansalmon](https://github.com/ronansalmon)
* [rremizov](https://github.com/rremizov)
* [sal0max](https://github.com/sal0max)
* [sebsauvage](https://github.com/sebsauvage)
* [shutosg](https://github.com/shutosg)
* [simon816](https://github.com/simon816)
* [Simounet](https://github.com/Simounet)
* [somini](https://github.com/somini)
* [SpangleLabs](https://github.com/SpangleLabs)
* [squeek502](https://github.com/squeek502)
* [stjohnjohnson](https://github.com/stjohnjohnson)
* [Stopka](https://github.com/Stopka)
* [Strubbl](https://github.com/Strubbl)
* [sublimz](https://github.com/sublimz)
* [sunchaserinfo](https://github.com/sunchaserinfo)
* [SuperSandro2000](https://github.com/SuperSandro2000)
* [sysadminstory](https://github.com/sysadminstory)
* [t0stiman](https://github.com/t0stiman)
* [tameroski](https://github.com/tameroski)
* [teromene](https://github.com/teromene)
* [tgkenney](https://github.com/tgkenney)
* [thefranke](https://github.com/thefranke)
* [ThePadawan](https://github.com/ThePadawan)
* [TheRadialActive](https://github.com/TheRadialActive)
* [theScrabi](https://github.com/theScrabi)
* [thezeroalpha](https://github.com/thezeroalpha)
* [timendum](https://github.com/timendum)
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
* [tomaszkane](https://github.com/tomaszkane)
* [TReKiE](https://github.com/TReKiE)
* [triatic](https://github.com/triatic)
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
* [WalterBarrett](https://github.com/WalterBarrett)
* [wtuuju](https://github.com/wtuuju)
* [xurxof](https://github.com/xurxof)
* [yamanq](https://github.com/yamanq)
* [yardenac](https://github.com/yardenac)
* [ymeister](https://github.com/ymeister)
* [yue-dongchen](https://github.com/yue-dongchen)
* [ZeNairolf](https://github.com/ZeNairolf)
Licenses
===
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](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)
* [`Laravel framework`](https://github.com/laravel/framework/) licensed under the [MIT License](https://opensource.org/licenses/MIT)
* [`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)
## Rant
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)
Rant
===
*Dear so-called "social" websites.*

View File

@@ -1,5 +1,4 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
@@ -7,9 +6,9 @@
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
/**
@@ -22,59 +21,116 @@
* - Returns a responsive web page that automatically checks all whitelisted
* bridges (using JavaScript) if no bridge is specified.
*/
class ConnectivityAction implements ActionInterface
{
private BridgeFactory $bridgeFactory;
class ConnectivityAction extends ActionAbstract {
public function execute() {
public function __construct()
{
$this->bridgeFactory = new BridgeFactory();
}
if(!Debug::isEnabled()) {
returnError('This action is only available in debug mode!');
}
public function execute(array $request)
{
if (!Debug::isEnabled()) {
throw new \Exception('This action is only available in debug mode!');
}
if(!isset($this->userData['bridge'])) {
$this->returnEntryPage();
return;
}
if (!isset($request['bridge'])) {
return render_template('connectivity.html.php');
}
$bridgeName = $this->userData['bridge'];
$bridgeClassName = $this->bridgeFactory->createBridgeClassName($request['bridge']);
$this->reportBridgeConnectivity($bridgeName);
return $this->reportBridgeConnectivity($bridgeClassName);
}
}
private function reportBridgeConnectivity($bridgeClassName)
{
if (!$this->bridgeFactory->isEnabled($bridgeClassName)) {
throw new \Exception('Bridge is not whitelisted!');
}
/**
* Generates a report about the bridge connectivity status and sends it back
* to the user.
*
* The report is generated as Json-formatted string in the format
* {
* "bridge": "<bridge-name>",
* "successful": true/false
* }
*
* @param string $bridgeName Name of the bridge to generate the report for
* @return void
*/
private function reportBridgeConnectivity($bridgeName) {
$retVal = [
'bridge' => $bridgeClassName,
'successful' => false,
'http_code' => 200,
];
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
$bridge = $this->bridgeFactory->create($bridgeClassName);
$curl_opts = [
CURLOPT_CONNECTTIMEOUT => 5
];
try {
$reply = getContents($bridge::URI, [], $curl_opts, true);
if(!$bridgeFac->isWhitelisted($bridgeName)) {
header('Content-Type: text/html');
returnServerError('Bridge is not whitelisted!');
}
if ($reply['code'] === 200) {
$retVal['successful'] = true;
if (strpos(implode('', $reply['status_lines']), '301 Moved Permanently')) {
$retVal['http_code'] = 301;
}
}
} catch (\Exception $e) {
$retVal['successful'] = false;
}
header('Content-Type: text/json');
return new Response(Json::encode($retVal), 200, ['Content-Type' => 'text/json']);
}
$retVal = array(
'bridge' => $bridgeName,
'successful' => false,
'http_code' => 200,
);
$bridge = $bridgeFac->create($bridgeName);
if($bridge === false) {
echo json_encode($retVal);
return;
}
$curl_opts = array(
CURLOPT_CONNECTTIMEOUT => 5
);
try {
$reply = getContents($bridge::URI, array(), $curl_opts, true);
if($reply) {
$retVal['successful'] = true;
if (isset($reply['header'])) {
if (strpos($reply['header'], 'HTTP/1.1 301 Moved Permanently') !== false) {
$retVal['http_code'] = 301;
}
}
}
} catch(Exception $e) {
$retVal['successful'] = false;
}
echo json_encode($retVal);
}
private function returnEntryPage() {
echo <<<EOD
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="static/bootstrap.min.css">
<link
rel="stylesheet"
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
crossorigin="anonymous">
<link rel="stylesheet" href="static/connectivity.css">
<script src="static/connectivity.js" type="text/javascript"></script>
</head>
<body>
<div id="main-content" class="container">
<div class="progress">
<div class="progress-bar" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
</div>
<div id="status-message" class="sticky-top alert alert-primary alert-dismissible fade show" role="alert">
<i id="status-icon" class="fas fa-sync"></i>
<span>...</span>
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="stopConnectivityChecks()">
<span aria-hidden="true">&times;</span>
</button>
</div>
<input type="text" class="form-control" id="search" onkeyup="search()" placeholder="Search for bridge..">
</div>
</body>
</html>
EOD;
}
}

View File

@@ -1,5 +1,4 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
@@ -7,47 +6,48 @@
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class DetectAction implements ActionInterface
{
public function execute(array $request)
{
$targetURL = $request['url'] ?? null;
$format = $request['format'] ?? null;
class DetectAction extends ActionAbstract {
public function execute() {
$targetURL = $this->userData['url']
or returnClientError('You must specify a url!');
if (!$targetURL) {
throw new \Exception('You must specify a url!');
}
if (!$format) {
throw new \Exception('You must specify a format!');
}
$format = $this->userData['format']
or returnClientError('You must specify a format!');
$bridgeFactory = new BridgeFactory();
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
continue;
}
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
$bridge = $bridgeFactory->create($bridgeClassName);
if(!$bridgeFac->isWhitelisted($bridgeName)) {
continue;
}
$bridgeParams = $bridge->detectParameters($targetURL);
$bridge = $bridgeFac->create($bridgeName);
if (is_null($bridgeParams)) {
continue;
}
if($bridge === false) {
continue;
}
$bridgeParams['bridge'] = $bridgeClassName;
$bridgeParams['format'] = $format;
$bridgeParams = $bridge->detectParameters($targetURL);
$url = '?action=display&' . http_build_query($bridgeParams);
return new Response('', 301, ['Location' => $url]);
}
if(is_null($bridgeParams)) {
continue;
}
throw new \Exception('No bridge found for given URL: ' . $targetURL);
}
$bridgeParams['bridge'] = $bridgeName;
$bridgeParams['format'] = $format;
header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
die();
}
returnClientError('No bridge found for given URL: ' . $targetURL);
}
}

View File

@@ -1,5 +1,4 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
@@ -7,254 +6,255 @@
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class DisplayAction implements ActionInterface
{
public function execute(array $request)
{
if (Configuration::getConfig('system', 'enable_maintenance_mode')) {
return new Response('503 Service Unavailable', 503);
}
class DisplayAction extends ActionAbstract {
private function get_return_code($error) {
$returnCode = $error->getCode();
if ($returnCode === 301 || $returnCode === 302) {
# Don't pass redirect codes to the exterior
$returnCode = 508;
}
return $returnCode;
}
$bridgeFactory = new BridgeFactory();
public function execute() {
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
$bridgeClassName = $bridgeFactory->createBridgeClassName($request['bridge'] ?? '');
$format = $this->userData['format']
or returnClientError('You must specify a format!');
$format = $request['format'] ?? null;
if (!$format) {
throw new \Exception('You must specify a format!');
}
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
throw new \Exception('This bridge is not whitelisted');
}
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
$formatFactory = new FormatFactory();
$format = $formatFactory->create($format);
// whitelist control
if(!$bridgeFac->isWhitelisted($bridge)) {
throw new \Exception('This bridge is not whitelisted', 401);
die;
}
$bridge = $bridgeFactory->create($bridgeClassName);
$bridge->loadConfiguration();
// Data retrieval
$bridge = $bridgeFac->create($bridge);
$bridge->loadConfiguration();
$noproxy = $request['_noproxy'] ?? null;
if (
Configuration::getConfig('proxy', 'url')
&& Configuration::getConfig('proxy', 'by_bridge')
&& $noproxy
) {
// This const is only used once in getContents()
define('NOPROXY', true);
}
$noproxy = array_key_exists('_noproxy', $this->userData)
&& filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN);
$cacheTimeout = $request['_cache_timeout'] ?? null;
if (Configuration::getConfig('cache', 'custom_timeout') && $cacheTimeout) {
$cacheTimeout = (int) $cacheTimeout;
} else {
// At this point the query argument might still be in the url but it won't be used
$cacheTimeout = $bridge->getCacheTimeout();
}
if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
define('NOPROXY', true);
}
// Remove parameters that don't concern bridges
$bridge_params = array_diff_key(
$request,
array_fill_keys(
[
'action',
'bridge',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
],
''
)
);
// Cache timeout
$cache_timeout = -1;
if(array_key_exists('_cache_timeout', $this->userData)) {
// Remove parameters that don't concern caches
$cache_params = array_diff_key(
$request,
array_fill_keys(
[
'action',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
],
''
)
);
if(!CUSTOM_CACHE_TIMEOUT) {
unset($this->userData['_cache_timeout']);
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData);
header('Location: ' . $uri, true, 301);
die();
}
$cache = RssBridge::getCache();
$cache->setScope('');
$cache->setKey($cache_params);
// This cache purge will basically delete all cache items older than 24h, regardless of scope and key
$cache->purgeCache(86400);
$cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT);
$items = [];
$infos = [];
$mtime = $cache->getTime();
} else {
$cache_timeout = $bridge->getCacheTimeout();
}
if (
$mtime
&& (time() - $cacheTimeout < $mtime)
&& !Debug::isEnabled()
) {
// At this point we found the feed in the cache and debug mode is disabled
// Remove parameters that don't concern bridges
$bridge_params = array_diff_key(
$this->userData,
array_fill_keys(
array(
'action',
'bridge',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
), '')
);
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
// The client wants to know if the feed has changed since its last check
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if ($mtime <= $stime) {
$lastModified2 = gmdate('D, d M Y H:i:s ', $mtime) . 'GMT';
return new Response('', 304, ['Last-Modified' => $lastModified2]);
}
}
// Remove parameters that don't concern caches
$cache_params = array_diff_key(
$this->userData,
array_fill_keys(
array(
'action',
'format',
'_noproxy',
'_cache_timeout',
'_error_time'
), '')
);
// Load the feed from cache and prepare it
$cached = $cache->loadData();
if (isset($cached['items']) && isset($cached['extraInfos'])) {
foreach ($cached['items'] as $item) {
$items[] = new FeedItem($item);
}
$infos = $cached['extraInfos'];
}
} else {
// At this point we did NOT find the feed in the cache or debug mode is enabled.
try {
$bridge->setDatas($bridge_params);
$bridge->collectData();
// Initialize cache
$cacheFac = new CacheFactory();
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
$cache->setScope('');
$cache->purgeCache(86400); // 24 hours
$cache->setKey($cache_params);
$items = $bridge->getItems();
$items = array();
$infos = array();
$mtime = $cache->getTime();
if (isset($items[0]) && is_array($items[0])) {
$feedItems = [];
foreach ($items as $item) {
$feedItems[] = new FeedItem($item);
}
$items = $feedItems;
}
$infos = [
'name' => $bridge->getName(),
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'icon' => $bridge->getIcon()
];
} catch (\Throwable $e) {
if ($e instanceof HttpException) {
// Produce a smaller log record for http exceptions
Logger::warning(sprintf('Exception in %s: %s', $bridgeClassName, create_sane_exception_message($e)));
} else {
// Log the exception
Logger::error(sprintf('Exception in %s', $bridgeClassName), ['e' => $e]);
}
if($mtime !== false
&& (time() - $cache_timeout < $mtime)
&& !Debug::isEnabled()) { // Load cached data
// Emit error only if we are passed the error report limit
$errorCount = self::logBridgeError($bridge->getName(), $e->getCode());
if ($errorCount >= Configuration::getConfig('error', 'report_limit')) {
if (Configuration::getConfig('error', 'output') === 'feed') {
// Emit the error as a feed item in a feed so that feed readers can pick it up
$items[] = $this->createFeedItemFromException($e, $bridge);
} elseif (Configuration::getConfig('error', 'output') === 'http') {
// Emit as a regular web response
throw $e;
}
}
}
// Send "Not Modified" response if client supports it
// Implementation based on https://stackoverflow.com/a/10847262
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
// Unfortunately need to set scope and key again because they might be modified
$cache->setScope('');
$cache->setKey($cache_params);
$cache->saveData([
'items' => array_map(function (FeedItem $item) {
return $item->toArray();
}, $items),
'extraInfos' => $infos
]);
}
if($mtime <= $stime) { // Cached data is older or same
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
die();
}
}
$format->setItems($items);
$format->setExtraInfos($infos);
$lastModified = $cache->getTime();
$format->setLastModified($lastModified);
$headers = [];
if ($lastModified) {
$headers['Last-Modified'] = gmdate('D, d M Y H:i:s ', $lastModified) . 'GMT';
}
$headers['Content-Type'] = $format->getMimeType() . '; charset=' . $format->getCharset();
return new Response($format->stringify(), 200, $headers);
}
$cached = $cache->loadData();
private function createFeedItemFromException($e, BridgeInterface $bridge): FeedItem
{
$item = new FeedItem();
if(isset($cached['items']) && isset($cached['extraInfos'])) {
foreach($cached['items'] as $item) {
$items[] = new \FeedItem($item);
}
// Create a unique identifier every 24 hours
$uniqueIdentifier = urlencode((int)(time() / 86400));
$itemTitle = sprintf('Bridge returned error %s! (%s)', $e->getCode(), $uniqueIdentifier);
$item->setTitle($itemTitle);
$item->setURI(get_current_url());
$item->setTimestamp(time());
$infos = $cached['extraInfos'];
}
// Create an item identifier for feed readers e.g. "staysafetv twitch videos_19389"
$item->setUid($bridge->getName() . '_' . $uniqueIdentifier);
} else { // Collect new data
$content = render_template(__DIR__ . '/../templates/bridge-error.html.php', [
'error' => render_template(__DIR__ . '/../templates/error.html.php', ['e' => $e]),
'searchUrl' => self::createGithubSearchUrl($bridge),
'issueUrl' => self::createGithubIssueUrl($bridge, $e, create_sane_exception_message($e)),
'maintainer' => $bridge->getMaintainer(),
]);
$item->setContent($content);
return $item;
}
try {
$bridge->setDatas($bridge_params);
$bridge->collectData();
private static function logBridgeError($bridgeName, $code)
{
$cache = RssBridge::getCache();
$cache->setScope('error_reporting');
$cache->setkey([$bridgeName . '_' . $code]);
$items = $bridge->getItems();
if ($report = $cache->loadData()) {
$report = Json::decode($report);
$report['time'] = time();
$report['count']++;
} else {
$report = [
'error' => $code,
'time' => time(),
'count' => 1,
];
}
$cache->saveData(Json::encode($report));
return $report['count'];
}
// Transform "legacy" items to FeedItems if necessary.
// Remove this code when support for "legacy" items ends!
if(isset($items[0]) && is_array($items[0])) {
$feedItems = array();
private static function createGithubIssueUrl($bridge, $e, string $message): string
{
return sprintf('https://github.com/RSS-Bridge/rss-bridge/issues/new?%s', http_build_query([
'title' => sprintf('%s failed with error %s', $bridge->getName(), $e->getCode()),
'body' => sprintf(
"```\n%s\n\n%s\n\nQuery string: %s\nVersion: %s\nOs: %s\nPHP version: %s\n```",
$message,
implode("\n", trace_to_call_points(trace_from_exception($e))),
$_SERVER['QUERY_STRING'] ?? '',
Configuration::getVersion(),
PHP_OS_FAMILY,
phpversion() ?: 'Unknown'
),
'labels' => 'Bridge-Broken',
'assignee' => $bridge->getMaintainer(),
]));
}
foreach($items as $item) {
$feedItems[] = new \FeedItem($item);
}
private static function createGithubSearchUrl($bridge): string
{
return sprintf(
'https://github.com/RSS-Bridge/rss-bridge/issues?q=%s',
urlencode('is:issue is:open ' . $bridge->getName())
);
}
$items = $feedItems;
}
$infos = array(
'name' => $bridge->getName(),
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'icon' => $bridge->getIcon()
);
} catch(Error $e) {
error_log($e);
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
if(Configuration::getConfig('error', 'output') === 'feed') {
$item = new \FeedItem();
// Create "new" error message every 24 hours
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
// Error 0 is a special case (i.e. "trying to get property of non-object")
if($e->getCode() === 0) {
$item->setTitle(
'Bridge encountered an unexpected situation! ('
. $this->userData['_error_time']
. ')'
);
} else {
$item->setTitle(
'Bridge returned error '
. $e->getCode()
. '! ('
. $this->userData['_error_time']
. ')'
);
}
$item->setURI(
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
. '?'
. http_build_query($this->userData)
);
$item->setTimestamp(time());
$item->setContent(buildBridgeException($e, $bridge));
$items[] = $item;
} elseif(Configuration::getConfig('error', 'output') === 'http') {
header('Content-Type: text/html', true, $this->get_return_code($e));
die(buildTransformException($e, $bridge));
}
}
} catch(Exception $e) {
error_log($e);
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
if(Configuration::getConfig('error', 'output') === 'feed') {
$item = new \FeedItem();
// Create "new" error message every 24 hours
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
$item->setURI(
(isset($_SERVER['REQUEST_URI']) ? parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) : '')
. '?'
. http_build_query($this->userData)
);
$item->setTitle(
'Bridge returned error '
. $e->getCode()
. '! ('
. $this->userData['_error_time']
. ')'
);
$item->setTimestamp(time());
$item->setContent(buildBridgeException($e, $bridge));
$items[] = $item;
} elseif(Configuration::getConfig('error', 'output') === 'http') {
header('Content-Type: text/html', true, $this->get_return_code($e));
die(buildTransformException($e, $bridge));
}
}
}
// Store data in cache
$cache->saveData(array(
'items' => array_map(function($i){ return $i->toArray(); }, $items),
'extraInfos' => $infos
));
}
// Data transformation
try {
$formatFac = new FormatFactory();
$formatFac->setWorkingDir(PATH_LIB_FORMATS);
$format = $formatFac->create($format);
$format->setItems($items);
$format->setExtraInfos($infos);
$format->setLastModified($cache->getTime());
$format->display();
} catch(Error $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
die(buildTransformException($e, $bridge));
} catch(Exception $e) {
error_log($e);
header('Content-Type: text/html', true, $e->getCode());
die(buildTransformException($e, $bridge));
}
}
}

View File

@@ -1,36 +0,0 @@
<?php
final class FrontpageAction implements ActionInterface
{
public function execute(array $request)
{
$showInactive = (bool) ($request['show_inactive'] ?? null);
$activeBridges = 0;
$bridgeFactory = new BridgeFactory();
$bridgeClassNames = $bridgeFactory->getBridgeClassNames();
$formatFactory = new FormatFactory();
$formats = $formatFactory->getFormatNames();
$body = '';
foreach ($bridgeClassNames as $bridgeClassName) {
if ($bridgeFactory->isEnabled($bridgeClassName)) {
$body .= BridgeCard::displayBridgeCard($bridgeClassName, $formats);
$activeBridges++;
} elseif ($showInactive) {
$body .= BridgeCard::displayBridgeCard($bridgeClassName, $formats, false) . PHP_EOL;
}
}
return render(__DIR__ . '/../templates/frontpage.html.php', [
'messages' => [],
'admin_email' => Configuration::getConfig('admin', 'email'),
'admin_telegram' => Configuration::getConfig('admin', 'telegram'),
'bridges' => $body,
'active_bridges' => $activeBridges,
'total_bridges' => count($bridgeClassNames),
'show_inactive' => $showInactive,
]);
}
}

View File

@@ -1,15 +0,0 @@
<?php
declare(strict_types=1);
class HealthAction implements ActionInterface
{
public function execute(array $request)
{
$response = [
'code' => 200,
'message' => 'all is good',
];
return new Response(Json::encode($response), 200, ['content-type' => 'application/json']);
}
}

View File

@@ -1,5 +1,4 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
@@ -7,36 +6,52 @@
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class ListAction implements ActionInterface
{
public function execute(array $request)
{
$list = new \stdClass();
$list->bridges = [];
$list->total = 0;
class ListAction extends ActionAbstract {
public function execute() {
$list = new StdClass();
$list->bridges = array();
$list->total = 0;
$bridgeFactory = new BridgeFactory();
$bridgeFac = new \BridgeFactory();
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
$bridge = $bridgeFactory->create($bridgeClassName);
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
$list->bridges[$bridgeClassName] = [
'status' => $bridgeFactory->isEnabled($bridgeClassName) ? 'active' : 'inactive',
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'name' => $bridge->getName(),
'icon' => $bridge->getIcon(),
'parameters' => $bridge->getParameters(),
'maintainer' => $bridge->getMaintainer(),
'description' => $bridge->getDescription()
];
}
$list->total = count($list->bridges);
return new Response(Json::encode($list), 200, ['Content-Type' => 'application/json']);
}
$bridge = $bridgeFac->create($bridgeName);
if($bridge === false) { // Broken bridge, show as inactive
$list->bridges[$bridgeName] = array(
'status' => 'inactive'
);
continue;
}
$status = $bridgeFac->isWhitelisted($bridgeName) ? 'active' : 'inactive';
$list->bridges[$bridgeName] = array(
'status' => $status,
'uri' => $bridge->getURI(),
'donationUri' => $bridge->getDonationURI(),
'name' => $bridge->getName(),
'icon' => $bridge->getIcon(),
'parameters' => $bridge->getParameters(),
'maintainer' => $bridge->getMaintainer(),
'description' => $bridge->getDescription()
);
}
$list->total = count($list->bridges);
header('Content-Type: application/json');
echo json_encode($list, JSON_PRETTY_PRINT);
}
}

View File

@@ -1,50 +0,0 @@
<?php
/**
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
* Atom feeds for websites that don't have one.
*
* For the full license information, please view the UNLICENSE file distributed
* with this source code.
*
* @package Core
* @license http://unlicense.org/ UNLICENSE
* @link https://github.com/rss-bridge/rss-bridge
*/
class SetBridgeCacheAction implements ActionInterface
{
public function execute(array $request)
{
$authenticationMiddleware = new ApiAuthenticationMiddleware();
$authenticationMiddleware($request);
$key = $request['key'] or returnClientError('You must specify key!');
$bridgeFactory = new BridgeFactory();
$bridgeClassName = $bridgeFactory->createBridgeClassName($request['bridge'] ?? '');
// whitelist control
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
throw new \Exception('This bridge is not whitelisted', 401);
die;
}
$bridge = $bridgeFactory->create($bridgeClassName);
$bridge->loadConfiguration();
$value = $request['value'];
$cache = RssBridge::getCache();
$cache->setScope(get_class($bridge));
if (!is_array($key)) {
// not sure if $key is an array when it comes in from request
$key = [$key];
}
$cache->setKey($key);
$cache->saveData($value);
header('Content-Type: text/plain');
echo 'done';
}
}

View File

@@ -1,8 +1,8 @@
{
"service": "Heroku",
"name": "rss-bridge-heroku",
"name": "RSS-Bridge",
"description": "RSS-Bridge is a PHP project capable of generating RSS and Atom feeds for websites which don't have one.",
"repository": "https://github.com/RSS-Bridge/rss-bridge?1651005770",
"repository": "https://github.com/RSS-Bridge/rss-bridge",
"keywords": ["php", "rss-bridge", "rss"]
}

View File

@@ -1,49 +1,45 @@
<?php
class ABCNewsBridge extends BridgeAbstract {
const NAME = 'ABC News Bridge';
const URI = 'https://www.abc.net.au';
const DESCRIPTION = 'Topics of the Australian Broadcasting Corporation';
const MAINTAINER = 'yue-dongchen';
class ABCNewsBridge extends BridgeAbstract
{
const NAME = 'ABC News Bridge';
const URI = 'https://www.abc.net.au';
const DESCRIPTION = 'Topics of the Australian Broadcasting Corporation';
const MAINTAINER = 'yue-dongchen';
const PARAMETERS = array(
array(
'topic' => array(
'type' => 'list',
'name' => 'Region',
'title' => 'Choose state',
'values' => array(
'ACT' => 'act',
'NSW' => 'nsw',
'NT' => 'nt',
'QLD' => 'qld',
'SA' => 'sa',
'TAS' => 'tas',
'VIC' => 'vic',
'WA' => 'wa'
),
)
)
);
const PARAMETERS = [
[
'topic' => [
'type' => 'list',
'name' => 'Region',
'title' => 'Choose state',
'values' => [
'ACT' => 'act',
'NSW' => 'nsw',
'NT' => 'nt',
'QLD' => 'qld',
'SA' => 'sa',
'TAS' => 'tas',
'VIC' => 'vic',
'WA' => 'wa'
],
]
]
];
public function collectData() {
$url = 'https://www.abc.net.au/news/' . $this->getInput('topic');
$html = getSimpleHTMLDOM($url)->find('.YAJzu._2FvRw.ZWhbj._3BZxh', 0);
$html = defaultLinkTo($html, $this->getURI());
public function collectData()
{
$url = sprintf('https://www.abc.net.au/news/%s', $this->getInput('topic'));
$dom = getSimpleHTMLDOM($url);
$dom = $dom->find('div[data-component="CardList"]', 0);
if (!$dom) {
throw new \Exception(sprintf('Unable to find css selector on `%s`', $url));
}
$dom = defaultLinkTo($dom, $this->getURI());
foreach ($dom->find('div[data-component="GenericCard"]') as $article) {
$a = $article->find('a', 0);
$this->items[] = [
'title' => $a->plaintext,
'uri' => $a->href,
'content' => $article->find('[data-component="CardDescription"]', 0)->plaintext,
'timestamp' => strtotime($article->find('time', 0)->datetime),
];
}
}
foreach($html->find('._2H7Su') as $article) {
$item = array();
$title = $article->find('._3T9Id.fmhNa.nsZdE._2c2Zy._1tOey._3EOTW', 0);
$item['title'] = $title->plaintext;
$item['uri'] = $title->href;
$item['content'] = $article->find('.rMkro._1cBaI._3PhF6._10YQT._1yL-m', 0)->plaintext;
$item['timestamp'] = strtotime($article->find('time', 0)->datetime);
$this->items[] = $item;
}
}
}

42
bridges/ABCTabsBridge.php Normal file
View File

@@ -0,0 +1,42 @@
<?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;
}
}
}

View File

@@ -1,116 +0,0 @@
<?php
class ABolaBridge extends BridgeAbstract
{
const NAME = 'A Bola';
const URI = 'https://abola.pt/';
const DESCRIPTION = 'Returns news from the Portuguese sports newspaper A BOLA.PT';
const MAINTAINER = 'rmscoelho';
const CACHE_TIMEOUT = 3600;
const PARAMETERS = [
[
'feed' => [
'name' => 'News Feed',
'type' => 'list',
'title' => 'Feeds from the Portuguese sports newspaper A BOLA.PT',
'values' => [
'Últimas' => 'Nnh/Noticias',
'Seleção Nacional' => 'Selecao/Noticias',
'Futebol Nacional' => [
'Notícias' => 'Nacional/Noticias',
'Primeira Liga' => 'Nacional/Liga/Noticias',
'Liga 2' => 'Nacional/Liga2/Noticias',
'Liga 3' => 'Nacional/Liga3/Noticias',
'Liga Revelação' => 'Nacional/Liga-Revelacao/Noticias',
'Campeonato de Portugal' => 'Nacional/Campeonato-Portugal/Noticias',
'Distritais' => 'Nacional/Distritais/Noticias',
'Taça de Portugal' => 'Nacional/TPortugal/Noticias',
'Futebol Feminino' => 'Nacional/FFeminino/Noticias',
'Futsal' => 'Nacional/Futsal/Noticias',
],
'Futebol Internacional' => [
'Notícias' => 'Internacional/Noticias/Noticias',
'Liga dos Campeões' => 'Internacional/Liga-dos-campeoes/Noticias',
'Liga Europa' => 'Internacional/Liga-europa/Noticias',
'Liga Conferência' => 'Internacional/Liga-conferencia/Noticias',
'Liga das Nações' => 'Internacional/Liga-das-nacoes/Noticias',
'UEFA Youth League' => 'Internacional/Uefa-Youth-League/Noticias',
],
'Mercado' => 'Mercado',
'Modalidades' => 'Modalidades/Noticias',
'Motores' => 'Motores/Noticias',
]
]
]
];
public function getIcon()
{
return 'https://abola.pt/img/icons/favicon-96x96.png';
}
public function getName()
{
return !is_null($this->getKey('feed')) ? self::NAME . ' | ' . $this->getKey('feed') : self::NAME;
}
public function getURI()
{
return self::URI . $this->getInput('feed');
}
public function collectData()
{
$url = sprintf('https://abola.pt/%s', $this->getInput('feed'));
$dom = getSimpleHTMLDOM($url);
if ($this->getInput('feed') !== 'Mercado') {
$dom = $dom->find('div#body_Todas1_upNoticiasTodas', 0);
} else {
$dom = $dom->find('div#body_NoticiasMercado_upNoticiasTodas', 0);
}
if (!$dom) {
throw new \Exception(sprintf('Unable to find css selector on `%s`', $url));
}
$dom = defaultLinkTo($dom, $this->getURI());
foreach ($dom->find('div.media') as $key => $article) {
//Get thumbnail
$image = $article->find('.media-img', 0)->style;
$image = preg_replace('/background-image: url\(/i', '', $image);
$image = substr_replace($image, '', -4);
$image = preg_replace('/https:\/\//i', '', $image);
$image = preg_replace('/www\./i', '', $image);
$image = preg_replace('/\/\//', '/', $image);
$image = preg_replace('/\/\/\//', '//', $image);
$image = substr($image, 7);
$image = 'https://' . $image;
$image = preg_replace('/ptimg/', 'pt/img', $image);
$image = preg_replace('/\/\/bola/', 'www.abola', $image);
//Timestamp
$date = date('Y/m/d');
if (!is_null($article->find("span#body_Todas1_rptNoticiasTodas_lblData_$key", 0))) {
$date = $article->find("span#body_Todas1_rptNoticiasTodas_lblData_$key", 0)->plaintext;
$date = preg_replace('/\./', '/', $date);
}
$time = $article->find("span#body_Todas1_rptNoticiasTodas_lblHora_$key", 0)->plaintext;
$date = explode('/', $date);
$time = explode(':', $time);
$year = $date[0];
$month = $date[1];
$day = $date[2];
$hour = $time[0];
$minute = $time[1];
$timestamp = mktime($hour, $minute, 0, $month, $day, $year);
//Content
$image = '<img src="' . $image . '" alt="' . $article->find('h4 span', 0)->plaintext . '" />';
$description = '<p>' . $article->find('.media-texto > span', 0)->plaintext . '</p>';
$content = $image . '</br>' . $description;
$a = $article->find('.media-body > a', 0);
$this->items[] = [
'title' => $a->find('h4 span', 0)->plaintext,
'uri' => $a->href,
'content' => $content,
'timestamp' => $timestamp,
];
}
}
}

View File

@@ -1,134 +1,118 @@
<?php
class AO3Bridge extends BridgeAbstract
{
const NAME = 'AO3';
const URI = 'https://archiveofourown.org/';
const CACHE_TIMEOUT = 1800;
const DESCRIPTION = 'Returns works or chapters from Archive of Our Own';
const MAINTAINER = 'Obsidienne';
const PARAMETERS = [
'List' => [
'url' => [
'name' => 'url',
'required' => true,
// Example: F/F tag, complete works only
'exampleValue' => 'https://archiveofourown.org/works?work_search[complete]=T&tag_id=F*s*F',
],
],
'Bookmarks' => [
'user' => [
'name' => 'user',
'required' => true,
// Example: Nyaaru's bookmarks
'exampleValue' => 'Nyaaru',
],
],
'Work' => [
'id' => [
'name' => 'id',
'required' => true,
// Example: latest chapters from A Better Past by LysSerris
'exampleValue' => '18181853',
],
]
];
class AO3Bridge extends BridgeAbstract {
const NAME = 'AO3';
const URI = 'https://archiveofourown.org/';
const CACHE_TIMEOUT = 1800;
const DESCRIPTION = 'Returns works or chapters from Archive of Our Own';
const MAINTAINER = 'Obsidienne';
const PARAMETERS = array(
'List' => array(
'url' => array(
'name' => 'url',
'required' => true,
// Example: F/F tag, complete works only
'exampleValue' => 'https://archiveofourown.org/works?work_search[complete]=T&tag_id=F*s*F',
),
),
'Bookmarks' => array(
'user' => array(
'name' => 'user',
'required' => true,
// Example: Nyaaru's bookmarks
'exampleValue' => 'Nyaaru',
),
),
'Work' => array(
'id' => array(
'name' => 'id',
'required' => true,
// Example: latest chapters from A Better Past by LysSerris
'exampleValue' => '18181853',
),
)
);
public function collectData()
{
switch ($this->queriedContext) {
case 'Bookmarks':
$user = $this->getInput('user');
$this->title = $user;
$url = self::URI
. '/users/' . $user
. '/bookmarks?bookmark_search[sort_column]=bookmarkable_date';
$this->collectList($url);
break;
case 'List':
$this->collectList($this->getInput('url'));
break;
case 'Work':
$this->collectWork($this->getInput('id'));
break;
}
}
// Feed for lists of works (e.g. recent works, search results, filtered tags,
// bookmarks, series, collections).
private function collectList($url) {
$html = getSimpleHTMLDOM($url);
$html = defaultLinkTo($html, self::URI);
/**
* Feed for lists of works (e.g. recent works, search results, filtered tags,
* bookmarks, series, collections).
*/
private function collectList($url)
{
$html = getSimpleHTMLDOM($url);
$html = defaultLinkTo($html, self::URI);
foreach($html->find('.index.group > li') as $element) {
$item = array();
foreach ($html->find('.index.group > li') as $element) {
$item = [];
$title = $element->find('div h4 a', 0);
if (!isset($title)) continue; // discard deleted works
$item['title'] = $title->plaintext;
$item['content'] = $element;
$item['uri'] = $title->href;
$title = $element->find('div h4 a', 0);
if (!isset($title)) {
continue; // discard deleted works
}
$item['title'] = $title->plaintext;
$item['content'] = $element;
$item['uri'] = $title->href;
$strdate = $element->find('div p.datetime', 0)->plaintext;
$item['timestamp'] = strtotime($strdate);
$strdate = $element->find('div p.datetime', 0)->plaintext;
$item['timestamp'] = strtotime($strdate);
$chapters = $element->find('dl dd.chapters', 0);
// bookmarked series and external works do not have a chapters count
$chapters = (isset($chapters) ? $chapters->plaintext : 0);
$item['uid'] = $item['uri'] . "/$strdate/$chapters";
$chapters = $element->find('dl dd.chapters', 0);
// bookmarked series and external works do not have a chapters count
$chapters = (isset($chapters) ? $chapters->plaintext : 0);
$item['uid'] = $item['uri'] . "/$strdate/$chapters";
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
// Feed for recent chapters of a specific work.
private function collectWork($id) {
$url = self::URI . "/works/$id/navigate";
$html = getSimpleHTMLDOM($url);
$html = defaultLinkTo($html, self::URI);
/**
* Feed for recent chapters of a specific work.
*/
private function collectWork($id)
{
$url = self::URI . "/works/$id/navigate";
$response = _http_request($url, ['useragent' => 'rss-bridge bot (https://github.com/RSS-Bridge/rss-bridge)']);
$html = \str_get_html($response['body']);
$html = defaultLinkTo($html, self::URI);
$this->title = $html->find('h2 a', 0)->plaintext;
$this->title = $html->find('h2 a', 0)->plaintext;
foreach($html->find('ol.index.group > li') as $element) {
$item = array();
foreach ($html->find('ol.index.group > li') as $element) {
$item = [];
$item['title'] = $element->find('a', 0)->plaintext;
$item['content'] = $element;
$item['uri'] = $element->find('a', 0)->href;
$item['title'] = $element->find('a', 0)->plaintext;
$item['content'] = $element;
$item['uri'] = $element->find('a', 0)->href;
$strdate = $element->find('span.datetime', 0)->plaintext;
$strdate = str_replace('(', '', $strdate);
$strdate = str_replace(')', '', $strdate);
$item['timestamp'] = strtotime($strdate);
$strdate = $element->find('span.datetime', 0)->plaintext;
$strdate = str_replace('(', '', $strdate);
$strdate = str_replace(')', '', $strdate);
$item['timestamp'] = strtotime($strdate);
$item['uid'] = $item['uri'] . "/$strdate";
$item['uid'] = $item['uri'] . "/$strdate";
$this->items[] = $item;
}
$this->items[] = $item;
}
$this->items = array_reverse($this->items);
}
$this->items = array_reverse($this->items);
}
public function collectData() {
switch($this->queriedContext) {
case 'Bookmarks':
$user = $this->getInput('user');
$this->title = $user;
$url = self::URI
. '/users/' . $user
. '/bookmarks?bookmark_search[sort_column]=bookmarkable_date';
return $this->collectList($url);
case 'List': return $this->collectList(
$this->getInput('url')
);
case 'Work': return $this->collectWork(
$this->getInput('id')
);
}
}
public function getName()
{
$name = parent::getName() . " $this->queriedContext";
if (isset($this->title)) {
$name .= " - $this->title";
}
return $name;
}
public function getName() {
$name = parent::getName() . " $this->queriedContext";
if (isset($this->title)) $name .= " - $this->title";
return $name;
}
public function getIcon()
{
return self::URI . '/favicon.ico';
}
public function getIcon() {
return self::URI . '/favicon.ico';
}
}

View File

@@ -1,158 +0,0 @@
<?php
class ARDAudiothekBridge extends BridgeAbstract
{
const NAME = 'ARD-Audiothek Bridge';
const URI = 'https://www.ardaudiothek.de';
const DESCRIPTION = 'Feed of any show in the ARD-Audiothek, specified by its path';
const MAINTAINER = 'Mar-Koeh';
/*
* The URL Prefix of the API
* @const APIENDPOINT https-URL of the used endpoint, ending in `/`
*/
const APIENDPOINT = 'https://api.ardaudiothek.de/';
/*
* The requested width of the preview image
* 448 and 128 have been observed on the wild
* @const IMAGEWIDTH width in px of the preview image
*/
const IMAGEWIDTH = 448;
/*
* Placeholder that will be replace by IMAGEWIDTH in the preview image URL
* @const IMAGEWIDTHPLACEHOLDER
*/
const IMAGEWIDTHPLACEHOLDER = '{width}';
/*
* File extension appended to image link in $this->icon
* @const IMAGEEXTENSION
*/
const IMAGEEXTENSION = '.jpg';
const PARAMETERS = [
[
'path' => [
'name' => 'Show Link or ID',
'required' => true,
'title' => 'Link to the show page or just its numeric suffix',
'defaultValue' => 'https://www.ardaudiothek.de/sendung/kalk-welk/10777871/'
],
'limit' => self::LIMIT,
]
];
/**
* Holds the title of the current show
*
* @var string
*/
private $title;
/**
* Holds the URI of the show
*
* @var string
*/
private $uri;
/**
* Holds the icon of the feed
*
*/
private $icon;
public function collectData()
{
$oldTz = date_default_timezone_get();
date_default_timezone_set('Europe/Berlin');
$pathComponents = explode('/', $this->getInput('path'));
if (empty($pathComponents)) {
returnClientError('Path may not be empty');
}
if (count($pathComponents) < 2) {
$showID = $pathComponents[0];
} else {
$lastKey = count($pathComponents) - 1;
$showID = $pathComponents[$lastKey];
if (strlen($showID) === 0) {
$showID = $pathComponents[$lastKey - 1];
}
}
$url = self::APIENDPOINT . 'programsets/' . $showID . '/';
$rawJSON = getContents($url);
$processedJSON = json_decode($rawJSON)->data->programSet;
$limit = $this->getInput('limit');
$answerLength = 1;
$offset = 0;
$numberOfElements = 1;
while ($answerLength != 0 && $offset < $numberOfElements && (is_null($limit) || $offset < $limit)) {
$rawJSON = getContents($url . '?offset=' . $offset);
$processedJSON = json_decode($rawJSON)->data->programSet;
$answerLength = count($processedJSON->items->nodes);
$offset = $offset + $answerLength;
$numberOfElements = $processedJSON->numberOfElements;
foreach ($processedJSON->items->nodes as $audio) {
$item = [];
$item['uri'] = $audio->sharingUrl;
$item['title'] = $audio->title;
$imageSquare = str_replace(self::IMAGEWIDTHPLACEHOLDER, self::IMAGEWIDTH, $audio->image->url1X1);
$image = str_replace(self::IMAGEWIDTHPLACEHOLDER, self::IMAGEWIDTH, $audio->image->url);
$item['enclosures'] = [
$audio->audios[0]->url,
$imageSquare
];
// synopsis in list is shortened, full synopsis is available using one request per item
$item['content'] = '<img src="' . $image . '" /><p>' . $audio->synopsis . '</p>';
$item['timestamp'] = $audio->publicationStartDateAndTime;
$item['uid'] = $audio->id;
$item['author'] = $audio->programSet->publicationService->title;
$item['categories'] = [ $audio->programSet->editorialCategories->title ];
$this->items[] = $item;
}
}
$this->title = $processedJSON->title;
$this->uri = $processedJSON->sharingUrl;
$this->icon = str_replace(self::IMAGEWIDTHPLACEHOLDER, self::IMAGEWIDTH, $processedJSON->image->url1X1);
// add image file extension to URL so icon is shown in generated RSS feeds, see
// https://github.com/RSS-Bridge/rss-bridge/blob/4aed05c7b678b5673386d61374bba13637d15487/formats/MrssFormat.php#L76
$this->icon = $this->icon . self::IMAGEEXTENSION;
$this->items = array_slice($this->items, 0, $limit);
date_default_timezone_set($oldTz);
}
/** {@inheritdoc} */
public function getURI()
{
if (!empty($this->uri)) {
return $this->uri;
}
return parent::getURI();
}
/** {@inheritdoc} */
public function getName()
{
if (!empty($this->title)) {
return $this->title;
}
return parent::getName();
}
/** {@inheritdoc} */
public function getIcon()
{
if (!empty($this->icon)) {
return $this->icon;
}
return parent::getIcon();
}
}

View File

@@ -1,98 +1,95 @@
<?php
class ARDMediathekBridge extends BridgeAbstract {
const NAME = 'ARD-Mediathek Bridge';
const URI = 'https://www.ardmediathek.de';
const DESCRIPTION = 'Feed of any series in the ARD-Mediathek, specified by its path';
const MAINTAINER = 'yue-dongchen';
/*
* Number of Items to be requested from ARDmediathek API
* 12 has been observed on the wild
* 29 is the highest successfully tested value
* More Items could be fetched via pagination
* The JSON-field pagination holds more information on that
* @const PAGESIZE number of requested items
*/
const PAGESIZE = 29;
/*
* The URL Prefix of the (Webapp-)API
* @const APIENDPOINT https-URL of the used endpoint
*/
const APIENDPOINT = 'https://api.ardmediathek.de/page-gateway/widgets/ard/asset/';
/*
* The URL prefix of the video link
* URLs from the webapp include a slug containing titles of show, episode, and tv station.
* It seems to work without that.
* @const VIDEOLINKPREFIX https-URL prefix of video links
*/
const VIDEOLINKPREFIX = 'https://www.ardmediathek.de/video/';
/*
* The requested width of the preview image
* 432 has been observed on the wild
* The webapp seems to also compute and add the height value
* It seems to works without that.
* @const IMAGEWIDTH width in px of the preview image
*/
const IMAGEWIDTH = 432;
/*
* Placeholder that will be replace by IMAGEWIDTH in the preview image URL
* @const IMAGEWIDTHPLACEHOLDER
*/
const IMAGEWIDTHPLACEHOLDER = '{width}';
class ARDMediathekBridge extends BridgeAbstract
{
const NAME = 'ARD-Mediathek Bridge';
const URI = 'https://www.ardmediathek.de';
const DESCRIPTION = 'Feed of any series in the ARD-Mediathek, specified by its path';
const MAINTAINER = 'yue-dongchen';
/*
* Number of Items to be requested from ARDmediathek API
* 12 has been observed on the wild
* 29 is the highest successfully tested value
* More Items could be fetched via pagination
* The JSON-field pagination holds more information on that
* @const PAGESIZE number of requested items
*/
const PAGESIZE = 29;
/*
* The URL Prefix of the (Webapp-)API
* @const APIENDPOINT https-URL of the used endpoint
*/
const APIENDPOINT = 'https://api.ardmediathek.de/page-gateway/widgets/ard/asset/';
/*
* The URL prefix of the video link
* URLs from the webapp include a slug containing titles of show, episode, and tv station.
* It seems to work without that.
* @const VIDEOLINKPREFIX https-URL prefix of video links
*/
const VIDEOLINKPREFIX = 'https://www.ardmediathek.de/video/';
/*
* The requested width of the preview image
* 432 has been observed on the wild
* The webapp seems to also compute and add the height value
* It seems to works without that.
* @const IMAGEWIDTH width in px of the preview image
*/
const IMAGEWIDTH = 432;
/*
* Placeholder that will be replace by IMAGEWIDTH in the preview image URL
* @const IMAGEWIDTHPLACEHOLDER
*/
const IMAGEWIDTHPLACEHOLDER = '{width}';
const PARAMETERS = array(
array(
'path' => array(
'name' => 'Show Link or ID',
'required' => true,
'title' => 'Link to the show page or just its alphanumeric suffix',
'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/'
)
)
);
const PARAMETERS = [
[
'path' => [
'name' => 'Show Link or ID',
'required' => true,
'title' => 'Link to the show page or just its alphanumeric suffix',
'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/'
]
]
];
public function collectData() {
$oldTz = date_default_timezone_get();
public function collectData()
{
$oldTz = date_default_timezone_get();
date_default_timezone_set('Europe/Berlin');
date_default_timezone_set('Europe/Berlin');
$pathComponents = explode('/', $this->getInput('path'));
if (empty($pathComponents)) {
returnClientError('Path may not be empty');
}
if (count($pathComponents) < 2) {
$showID = $pathComponents[0];
} else {
$lastKey = count($pathComponents) - 1;
$showID = $pathComponents[$lastKey];
if (strlen($showID) === 0) {
$showID = $pathComponents[$lastKey - 1];
}
}
$pathComponents = explode('/', $this->getInput('path'));
if (empty($pathComponents)) {
returnClientError('Path may not be empty');
}
if (count($pathComponents) < 2) {
$showID = $pathComponents[0];
} else {
$lastKey = count($pathComponents) - 1;
$showID = $pathComponents[$lastKey];
if (strlen($showID) === 0) {
$showID = $pathComponents[$lastKey - 1];
}
}
$url = SELF::APIENDPOINT . $showID . '/?pageSize=' . SELF::PAGESIZE;
$rawJSON = getContents($url);
$processedJSON = json_decode($rawJSON);
$url = self::APIENDPOINT . $showID . '/?pageSize=' . self::PAGESIZE;
$rawJSON = getContents($url);
$processedJSON = json_decode($rawJSON);
foreach($processedJSON->teasers as $video) {
$item = array();
// there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId
$item['uri'] = SELF::VIDEOLINKPREFIX . $video->id . '/';
// there is also ->mediumTitle and ->shortTitle
$item['title'] = $video->longTitle;
// in the test, aspect16x9 was the only child of images, not sure whether that is always true
$item['enclosures'] = array(
str_replace(SELF::IMAGEWIDTHPLACEHOLDER, SELF::IMAGEWIDTH, $video->images->aspect16x9->src)
);
$item['content'] = '<img src="' . $item['enclosures'][0] . '" /><p>';
$item['timestamp'] = $video->broadcastedOn;
$item['uid'] = $video->id;
$item['author'] = $video->publicationService->name;
$this->items[] = $item;
}
foreach ($processedJSON->teasers as $video) {
$item = [];
// there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId
$item['uri'] = self::VIDEOLINKPREFIX . $video->id . '/';
// there is also ->mediumTitle and ->shortTitle
$item['title'] = $video->longTitle;
// in the test, aspect16x9 was the only child of images, not sure whether that is always true
$item['enclosures'] = [
str_replace(self::IMAGEWIDTHPLACEHOLDER, self::IMAGEWIDTH, $video->images->aspect16x9->src)
];
$item['content'] = '<img src="' . $item['enclosures'][0] . '" /><p>';
$item['timestamp'] = $video->broadcastedOn;
$item['uid'] = $video->id;
$item['author'] = $video->publicationService->name;
$this->items[] = $item;
}
date_default_timezone_set($oldTz);
}
date_default_timezone_set($oldTz);
}
}

View File

@@ -1,63 +1,55 @@
<?php
class ASRockNewsBridge extends BridgeAbstract {
const NAME = 'ASRock News Bridge';
const URI = 'https://www.asrock.com';
const DESCRIPTION = 'Returns latest news articles';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array();
class ASRockNewsBridge extends BridgeAbstract
{
const NAME = 'ASRock News Bridge';
const URI = 'https://www.asrock.com';
const DESCRIPTION = 'Returns latest news articles';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = [];
const CACHE_TIMEOUT = 3600; // 1 hour
const CACHE_TIMEOUT = 3600; // 1 hour
public function collectData() {
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp');
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp');
$html = defaultLinkTo($html, self::URI . '/news/');
$html = defaultLinkTo($html, self::URI . '/news/');
foreach ($html->find('div.inner > a') as $index => $a) {
$item = [];
foreach($html->find('div.inner > a') as $index => $a) {
$item = array();
$articlePath = $a->href;
$articlePath = $a->href;
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT);
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, self::CACHE_TIMEOUT);
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
$articlePageHtml = defaultLinkTo($articlePageHtml, self::URI);
$contents = $articlePageHtml->find('div.Contents', 0);
$contents = $articlePageHtml->find('div.Contents', 0);
$item['uri'] = $articlePath;
$item['title'] = $contents->find('h3', 0)->innertext;
$item['uri'] = $articlePath;
$item['title'] = $contents->find('h3', 0)->innertext;
$contents->find('h3', 0)->outertext = '';
$contents->find('h3', 0)->outertext = '';
$item['content'] = $contents->innertext;
$item['timestamp'] = $this->extractDate($a->plaintext);
$item['content'] = $contents->innertext;
$item['timestamp'] = $this->extractDate($a->plaintext);
$item['enclosures'][] = $a->find('img', 0)->src;
$this->items[] = $item;
$img = $a->find('img', 0);
if ($img) {
$item['enclosures'][] = $img->src;
}
if (count($this->items) >= 10) {
break;
}
}
}
$this->items[] = $item;
private function extractDate($text) {
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
if (count($this->items) >= 10) {
break;
}
}
}
$text = trim($text);
private function extractDate($text)
{
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
if (preg_match($dateRegex, $text, $matches)) {
return $matches[1];
}
$text = trim($text);
if (preg_match($dateRegex, $text, $matches)) {
return $matches[1];
}
return '';
}
return '';
}
}

View File

@@ -1,40 +1,24 @@
<?php
class AcrimedBridge extends FeedExpander {
class AcrimedBridge extends FeedExpander
{
const MAINTAINER = 'qwertygc';
const NAME = 'Acrimed Bridge';
const URI = 'https://www.acrimed.org/';
const CACHE_TIMEOUT = 4800; //2hours
const DESCRIPTION = 'Returns the newest articles';
const MAINTAINER = 'qwertygc';
const NAME = 'Acrimed Bridge';
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');
}
public function collectData()
{
$this->collectExpandableDatas(
static::URI . 'spip.php?page=backend',
$this->getInput('limit')
);
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
protected function parseItem($newsItem)
{
$item = parent::parseItem($newsItem);
$articlePage = getSimpleHTMLDOM($newsItem->link);
$article = sanitize($articlePage->find('article.article1', 0)->innertext);
$article = defaultLinkTo($article, static::URI);
$item['content'] = $article;
$articlePage = getSimpleHTMLDOM($newsItem->link);
$article = sanitize($articlePage->find('article.article1', 0)->innertext);
$article = defaultLinkTo($article, static::URI);
$item['content'] = $article;
return $item;
}
return $item;
}
}

View File

@@ -1,57 +1,54 @@
<?php
class AirBreizhBridge extends BridgeAbstract {
class AirBreizhBridge extends BridgeAbstract
{
const MAINTAINER = 'fanch317';
const NAME = 'Air Breizh';
const URI = 'https://www.airbreizh.asso.fr/';
const DESCRIPTION = 'Returns newests publications on Air Breizh';
const PARAMETERS = [
'Publications' => [
'theme' => [
'name' => 'Thematique',
'type' => 'list',
'values' => [
'Tout' => '',
'Rapport d\'activite' => 'rapport-dactivite',
'Etude' => 'etudes',
'Information' => 'information',
'Autres documents' => 'autres-documents',
'Plan Régional de Surveillance de la qualité de lair' => 'prsqa',
'Transport' => 'transport'
]
]
]
];
const MAINTAINER = 'fanch317';
const NAME = 'Air Breizh';
const URI = 'https://www.airbreizh.asso.fr/';
const DESCRIPTION = 'Returns newests publications on Air Breizh';
const PARAMETERS = array(
'Publications' => array(
'theme' => array(
'name' => 'Thematique',
'type' => 'list',
'values' => array(
'Tout' => '',
'Rapport d\'activite' => 'rapport-dactivite',
'Etude' => 'etudes',
'Information' => 'information',
'Autres documents' => 'autres-documents',
'Plan Régional de Surveillance de la qualité de lair' => 'prsqa',
'Transport' => 'transport'
)
)
)
);
public function getIcon()
{
return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png';
}
public function getIcon() {
return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png';
}
public function collectData()
{
$html = '';
$html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme'))
or returnClientError('No results for this query.');
public function collectData(){
$html = '';
$html = getSimpleHTMLDOM(static::URI . 'publications/?fwp_publications_thematiques=' . $this->getInput('theme'))
or returnClientError('No results for this query.');
foreach ($html->find('article') as $article) {
$item = [];
// Title
$item['title'] = $article->find('h2', 0)->plaintext;
// Author
$item['author'] = 'Air Breizh';
// Image
$imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src');
// Content preview
$item['content'] = '<img src="' . $imagelink . '" />
foreach ($html->find('article') as $article) {
$item = array();
// Title
$item['title'] = $article->find('h2', 0)->plaintext;
// Author
$item['author'] = 'Air Breizh';
// Image
$imagelink = $article->find('.card__image', 0)->find('img', 0)->getAttribute('src');
// Content preview
$item['content'] = '<img src="' . $imagelink . '" />
<br/>'
. $article->find('.card__text', 0)->plaintext;
// URL
$item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
// ID
$item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
$this->items[] = $item;
}
}
. $article->find('.card__text', 0)->plaintext;
// URL
$item['uri'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
// ID
$item['id'] = $article->find('.publi__buttons', 0)->find('a', 0)->getAttribute('href');
$this->items[] = $item;
}
}
}

View File

@@ -1,76 +1,73 @@
<?php
class AlbionOnlineBridge extends BridgeAbstract {
class AlbionOnlineBridge extends BridgeAbstract
{
const NAME = 'Albion Online Changelog';
const MAINTAINER = 'otakuf';
const URI = 'https://albiononline.com';
const DESCRIPTION = 'Returns the changes made to the Albion Online';
const CACHE_TIMEOUT = 3600; // 60min
const NAME = 'Albion Online Changelog';
const MAINTAINER = 'otakuf';
const URI = 'https://albiononline.com';
const DESCRIPTION = 'Returns the changes made to the Albion Online';
const CACHE_TIMEOUT = 3600; // 60min
const PARAMETERS = [ [
'postcount' => [
'name' => 'Limit',
'type' => 'number',
'required' => true,
'title' => 'Maximum number of items to return',
'defaultValue' => 5,
],
'language' => [
'name' => 'Language',
'type' => 'list',
'values' => [
'English' => 'en',
'Deutsch' => 'de',
'Polski' => 'pl',
'Français' => 'fr',
'Русский' => 'ru',
'Português' => 'pt',
'Español' => 'es',
],
'title' => 'Language of changelog posts',
'defaultValue' => 'en',
],
'full' => [
'name' => 'Full changelog',
'type' => 'checkbox',
'required' => false,
'title' => 'Enable to receive the full changelog post for each item'
],
]];
const PARAMETERS = array( array(
'postcount' => array(
'name' => 'Limit',
'type' => 'number',
'required' => true,
'title' => 'Maximum number of items to return',
'defaultValue' => 5,
),
'language' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'English' => 'en',
'Deutsch' => 'de',
'Polski' => 'pl',
'Français' => 'fr',
'Русский' => 'ru',
'Português' => 'pt',
'Español' => 'es',
),
'title' => 'Language of changelog posts',
'defaultValue' => 'en',
),
'full' => array(
'name' => 'Full changelog',
'type' => 'checkbox',
'required' => false,
'title' => 'Enable to receive the full changelog post for each item'
),
));
public function collectData()
{
$api = 'https://albiononline.com/';
// Example: https://albiononline.com/en/changelog/1/5
$url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount');
public function collectData() {
$api = 'https://albiononline.com/';
// Example: https://albiononline.com/en/changelog/1/5
$url = $api . $this->getInput('language') . '/changelog/1/' . $this->getInput('postcount');
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url);
foreach ($html->find('li') as $data) {
$item = [];
$item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href');
$item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]);
// Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language
//print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') );
//$item['timestamp'] = $this->extractDate($a->plaintext);
$item['author'] = 'albiononline.com';
if ($this->getInput('full')) {
$item['content'] = $this->getFullChangelog($item['uri']);
} else {
//$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext));
// Just use title, no info at all or use title and date, see above
$item['content'] = $item['title'];
}
$item['uid'] = hash('sha256', $item['title']);
$this->items[] = $item;
}
}
foreach ($html->find('li') as $data) {
$item = array();
$item['uri'] = self::URI . $data->find('a', 0)->getAttribute('href');
$item['title'] = trim(explode('|', $data->find('span', 0)->plaintext)[0]);
// Time below work only with en lang. Need to think about solution. May be separate request like getFullChangelog, but to english list for all language
//print_r( date_parse_from_format( 'M j, Y' , 'Sep 9, 2020') );
//$item['timestamp'] = $this->extractDate($a->plaintext);
$item['author'] = 'albiononline.com';
if($this->getInput('full')) {
$item['content'] = $this->getFullChangelog($item['uri']);
} else {
//$item['content'] = trim(preg_replace('/\s+/', ' ', $data->find('span', 0)->plaintext));
// Just use title, no info at all or use title and date, see above
$item['content'] = $item['title'];
}
$item['uid'] = hash('sha256', $item['title']);
$this->items[] = $item;
}
}
private function getFullChangelog($url)
{
$html = getSimpleHTMLDOMCached($url);
$html = defaultLinkTo($html, self::URI);
return $html->find('div.small-12.columns', 1)->innertext;
}
private function getFullChangelog($url) {
$html = getSimpleHTMLDOMCached($url);
$html = defaultLinkTo($html, self::URI);
return $html->find('div.small-12.columns', 1)->innertext;
}
}

View File

@@ -1,87 +0,0 @@
<?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 = [
'News' => [
'business' => [
'name' => 'Альфа Бизнес',
'type' => 'list',
'title' => 'В зависимости от выбора, возращает уведомления для" .
" клиентов физ. лиц либо для клиентов-юридических лиц и ИП',
'values' => [
'Новости' => 'news',
'Новости бизнеса' => 'newsBusiness'
],
'defaultValue' => 'news'
],
'fullContent' => [
'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 = [];
$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 = [
'Января', 'Февраля', 'Марта', 'Апреля', 'Мая', 'Июня',
'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря' ];
$enMonths = [
'January', 'February', 'March', 'April', 'May', 'June',
'July', 'August', 'September', 'October', 'November', 'December' ];
return str_replace($ruMonths, $enMonths, $date);
}
}

View File

@@ -1,85 +0,0 @@
<?php
class AllSidesBridge extends BridgeAbstract
{
const NAME = 'AllSides';
const URI = 'https://www.allsides.com';
const DESCRIPTION = 'Balanced news and media bias ratings.';
const MAINTAINER = 'Oliver Nutter';
const PARAMETERS = [
'global' => [
'limit' => [
'name' => 'Number of posts to return',
'type' => 'number',
'defaultValue' => 10,
'required' => false,
'title' => 'Zero or negative values return all posts (ignored if not fetching full article)',
],
'fetch' => [
'name' => 'Fetch full article content',
'type' => 'checkbox',
'defaultValue' => 'checked',
],
],
'Headline Roundups' => [],
];
private const ROUNDUPS_URI = self::URI . '/headline-roundups';
public function collectData()
{
switch ($this->queriedContext) {
case 'Headline Roundups':
$index = getSimpleHTMLDOM(self::ROUNDUPS_URI);
defaultLinkTo($index, self::ROUNDUPS_URI);
$entries = $index->find('table.views-table > tbody > tr');
$limit = (int) $this->getInput('limit');
$fetch = (bool) $this->getInput('fetch');
if ($limit > 0 && $fetch) {
$entries = array_slice($entries, 0, $limit);
}
foreach ($entries as $entry) {
$item = [
'title' => $entry->find('.views-field-name', 0)->text(),
'uri' => $entry->find('a', 0)->href,
'timestamp' => $entry->find('.date-display-single', 0)->content,
'author' => 'AllSides Staff',
];
if ($fetch) {
$article = getSimpleHTMLDOMCached($item['uri']);
defaultLinkTo($article, $item['uri']);
$item['content'] = $article->find('.story-id-page-description', 0);
foreach ($article->find('.page-tags a') as $tag) {
$item['categories'][] = $tag->text();
}
}
$this->items[] = $item;
}
break;
}
}
public function getName()
{
if ($this->queriedContext) {
return self::NAME . " - {$this->queriedContext}";
}
return self::NAME;
}
public function getURI()
{
switch ($this->queriedContext) {
case 'Headline Roundups':
return self::ROUNDUPS_URI;
}
return self::URI;
}
}

View File

@@ -1,144 +0,0 @@
<?php
class AllegroBridge extends BridgeAbstract
{
const NAME = 'Allegro';
const URI = 'https://www.allegro.pl';
const DESCRIPTION = 'Returns the search results from the Allegro.pl shopping and bidding portal';
const MAINTAINER = 'wrobelda';
const PARAMETERS = [[
'url' => [
'name' => 'Search URL',
'title' => 'Copy the URL from your browser\'s address bar after searching for your items and paste it here',
'exampleValue' => 'https://allegro.pl/kategoria/swieze-warzywa-cebula-318660',
'required' => true,
],
'sessioncookie' => [
'name' => 'The \'wdctx\' session cookie',
'title' => 'Paste the value of the \'wdctx\' cookie from your browser if you want to prevent Allegro imposing rate limits',
'pattern' => '^.{250,};?$',
// phpcs:ignore
'exampleValue' => 'v4.1-oCrmXTMqv2ppC21GTUCKLmUwRPP1ssQVALKuqwsZ1VXjcKgL2vO5TTRM5xMxS9GiyqxF1gAeyc-63dl0coUoBKXCXi_nAmr95yyqGpq2RAFoneZ4L399E8n6iYyemcuGARjAoSfjvLHJCEwvvHHynSgaxlFBu7hUnKfuy39zo9sSQdyTUjotJg3CAZ53q9v2raAnPCyGOAR4ytRILd9p24EJnxp7_oR0XbVPIo1hDa4WmjXFOxph8rHaO5tWd',
'required' => false,
],
'includeSponsoredOffers' => [
'type' => 'checkbox',
'name' => 'Include Sponsored Offers'
]
]];
public function getName()
{
parse_str(parse_url($this->getInput('url'), PHP_URL_QUERY), $fields);
if ($query = array_key_exists('string', $fields) ? urldecode($fields['string']) : false) {
return $query;
}
return parent::getName();
}
public function getURI()
{
return $this->getInput('url') ?? parent::getURI();
}
public function collectData()
{
# make sure we order by the most recently listed offers
$url = preg_replace('/([?&])order=[^&]+(&|$)/', '$1', $this->getInput('url'));
$url .= (parse_url($url, PHP_URL_QUERY) ? '&' : '?') . 'order=n';
$opts = [];
// If a session cookie is provided
if ($sessioncookie = $this->getInput('sessioncookie')) {
$opts[CURLOPT_COOKIE] = 'wdctx=' . $sessioncookie;
}
$html = getSimpleHTMLDOM($url, [], $opts);
# if no results found
if ($html->find('.mzmg_6m.m9qz_yo._6a66d_-fJr5')) {
return;
}
$results = $html->find('._6a66d_V7Lel article');
if (!$this->getInput('includeSponsoredOffers')) {
$results = array_filter($results, function ($node) {
return $node->{'data-analytics-view-label'} != 'showSponsoredItems';
});
}
foreach ($results as $post) {
$item = [];
$item['uri'] = $post->find('._6a66d_LX75-', 0)->href;
//TODO: port this over, whatever it does, from https://github.com/MK-PL/AllegroRSS
// if (arrayLinks.includes('events/clicks?')) {
// let sponsoredLink = new URL(arrayLinks).searchParams.get('redirect')
// arrayLinks = sponsoredLink.slice(0, sponsoredLink.indexOf('?'))
// }
$item['title'] = $post->find('._6a66d_LX75-', 0)->innertext;
$item['uid'] = $post->{'data-analytics-view-value'};
$descriptionPatterns = ['/<\s*dt[^>]*>\b/', '/<\/dt>/', '/<\s*dd[^>]*>\b/', '/<\/dd>/'];
$descriptionReplacements = ['<span>', ':</span> ', '<strong>', '&emsp;</strong> '];
$description = $post->find('.m7er_k4.mpof_5r.mpof_z0_s', 0)->innertext;
$descriptionPretty = preg_replace($descriptionPatterns, $descriptionReplacements, $description);
$buyNowAuction = $post->find('.mqu1_g3.mvrt_0.mgn2_12', 0)->innertext ?? '';
$buyNowAuction = str_replace('</span><span', '</span> <span', $buyNowAuction);
$auctionTimeLeft = $post->find('._6a66d_ImOzU', 0)->innertext ?? '';
$price = $post->find('._6a66d_6R3iN', 0)->plaintext;
$price = empty($auctionTimeLeft) ? $price : $price . '- kwota licytacji';
$image = $post->find('._6a66d_44ioA img', 0)->{'data-src'} ?: $post->find('._6a66d_44ioA img', 0)->src ?? false;
if ($image) {
$item['enclosures'] = [$image . '#.image'];
}
$offerExtraInfo = array_filter($post->find('.mqu1_g3.mgn2_12'), function ($node) {
return empty($node->find('.mvrt_0'));
});
$offerExtraInfo = $offerExtraInfo[0]->plaintext ?? '';
$isSmart = $post->find('._6a66d_TC2Zk', 0)->innertext ?? '';
if (str_contains($isSmart, 'z kurierem')) {
$offerExtraInfo .= ', Smart z kurierem';
} else {
$offerExtraInfo .= ', Smart';
}
$item['categories'] = [];
$parameters = $post->find('dd');
foreach ($parameters as $parameter) {
if (in_array(strtolower($parameter->innertext), ['brak', 'nie'])) {
continue;
}
$item['categories'][] = $parameter->innertext;
}
$item['content'] = $descriptionPretty
. '<div><strong>'
. $price
. '</strong></div><div>'
. $auctionTimeLeft
. '</div><div>'
. $buyNowAuction
. '</div><dl>'
. $offerExtraInfo
. '</dl><hr>';
$this->items[] = $item;
}
}
}

View File

@@ -1,107 +1,106 @@
<?php
class AllocineFRBridge extends BridgeAbstract {
class AllocineFRBridge extends BridgeAbstract
{
const MAINTAINER = 'superbaillot.net';
const NAME = 'Allo Cine Bridge';
const CACHE_TIMEOUT = 25200; // 7h
const URI = 'https://www.allocine.fr';
const DESCRIPTION = 'Bridge for allocine.fr';
const PARAMETERS = [ [
'category' => [
'name' => 'Emission',
'type' => 'list',
'title' => 'Sélectionner l\'emission',
'values' => [
'Faux Raccord' => 'faux-raccord',
'Fanzone' => 'fanzone',
'Game In Ciné' => 'game-in-cine',
'Pour la faire courte' => 'pour-la-faire-courte',
'Home Cinéma' => 'home-cinema',
'PILS - Par Ici Les Sorties' => 'pils-par-ici-les-sorties',
'AlloCiné : l\'émission, sur LeStream' => 'allocine-lemission-sur-lestream',
'Give Me Five' => 'give-me-five',
'Aviez-vous remarqué ?' => 'aviez-vous-remarque',
'Et paf, il est mort' => 'et-paf-il-est-mort',
'The Big Fan Theory' => 'the-big-fan-theory',
'Clichés' => 'cliches',
'Complètement...' => 'completement',
'#Fun Facts' => 'fun-facts',
'Origin Story' => 'origin-story',
]
]
]];
const MAINTAINER = 'superbaillot.net';
const NAME = 'Allo Cine Bridge';
const CACHE_TIMEOUT = 25200; // 7h
const URI = 'https://www.allocine.fr/';
const DESCRIPTION = 'Bridge for allocine.fr';
const PARAMETERS = array( array(
'category' => array(
'name' => 'Emission',
'type' => 'list',
'title' => 'Sélectionner l\'emission',
'values' => array(
'Faux Raccord' => 'faux-raccord',
'Fanzone' => 'fanzone',
'Game In Ciné' => 'game-in-cine',
'Pour la faire courte' => 'pour-la-faire-courte',
'Home Cinéma' => 'home-cinema',
'PILS - Par Ici Les Sorties' => 'pils-par-ici-les-sorties',
'AlloCiné : l\'émission, sur LeStream' => 'allocine-lemission-sur-lestream',
'Give Me Five' => 'give-me-five',
'Aviez-vous remarqué ?' => 'aviez-vous-remarque',
'Et paf, il est mort' => 'et-paf-il-est-mort',
'The Big Fan Theory' => 'the-big-fan-theory',
'Clichés' => 'cliches',
'Complètement...' => 'completement',
'#Fun Facts' => 'fun-facts',
'Origin Story' => 'origin-story',
)
)
));
public function getURI()
{
if (!is_null($this->getInput('category'))) {
$categories = [
'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/'
];
public function getURI(){
if(!is_null($this->getInput('category'))) {
$category = $this->getInput('category');
if (array_key_exists($category, $categories)) {
return static::URI . $this->getLastSeasonURI($categories[$category]);
} else {
returnClientError('Emission inconnue');
}
}
$categories = array(
'faux-raccord' => 'video/programme-12284/saison-37054/',
'fanzone' => 'video/programme-12298/saison-37059/',
'game-in-cine' => 'video/programme-12288/saison-22971/',
'pour-la-faire-courte' => 'video/programme-20960/saison-29678/',
'home-cinema' => 'video/programme-12287/saison-34703/',
'pils-par-ici-les-sorties' => 'video/programme-25789/saison-37253/',
'allocine-lemission-sur-lestream' => 'video/programme-25123/saison-36067/',
'give-me-five' => 'video/programme-21919/saison-34518/',
'aviez-vous-remarque' => 'video/programme-19518/saison-37084/',
'et-paf-il-est-mort' => 'video/programme-25113/saison-36657/',
'the-big-fan-theory' => 'video/programme-20403/saison-37419/',
'cliches' => 'video/programme-24834/saison-35591/',
'completement' => 'video/programme-23859/saison-34102/',
'fun-facts' => 'video/programme-23040/saison-32686/',
'origin-story' => 'video/programme-25667/saison-37041/'
);
return parent::getURI();
}
$category = $this->getInput('category');
if(array_key_exists($category, $categories)) {
return static::URI . $categories[$category];
} else {
returnClientError('Emission inconnue');
}
}
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;
}
return parent::getURI();
}
public function getName()
{
if (!is_null($this->getInput('category'))) {
return self::NAME . ' : ' . $this->getKey('category');
}
public function getName(){
if(!is_null($this->getInput('category'))) {
return self::NAME . ' : '
. array_search(
$this->getInput('category'),
self::PARAMETERS[$this->queriedContext]['category']['values']
);
}
return parent::getName();
}
return parent::getName();
}
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI());
public function collectData(){
foreach ($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) {
$item = [];
$html = getSimpleHTMLDOM($this->getURI());
$title = $element->find('a[class*=meta-title-link]', 0);
$content = trim(defaultLinkTo($element->outertext, static::URI));
$category = array_search(
$this->getInput('category'),
self::PARAMETERS[$this->queriedContext]['category']['values']
);
// Replace image 'src' with the one in 'data-src'
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content);
$content = preg_replace('@data-src=@', 'src=', $content);
foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) {
$item = array();
// Remove date in the content to prevent content update while the video is getting older
$content = preg_replace('@<div class="meta-sub light">.*<span>[^<]*</span>[^<]*</div>@', '', $content);
$title = $element->find('a[class*=meta-title-link]', 0);
$content = trim($element->outertext);
$item['content'] = $content;
$item['title'] = trim($title->innertext);
$item['uri'] = static::URI . '/' . substr($title->href, 1);
$this->items[] = $item;
}
}
// Replace image 'src' with the one in 'data-src'
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content);
$content = preg_replace('@data-src=@', 'src=', $content);
// Remove date in the content to prevent content update while the video is getting older
$content = preg_replace('@<div class="meta-sub light">.*<span>[^<]*</span>[^<]*</div>@', '', $content);
$item['content'] = $content;
$item['title'] = trim($title->innertext);
$item['uri'] = static::URI . substr($title->href, 1);
$this->items[] = $item;
}
}
}

View File

@@ -1,41 +0,0 @@
<?php
class AllocineFRSortiesBridge extends BridgeAbstract
{
const MAINTAINER = 'Simounet';
const NAME = 'AlloCiné Sorties Bridge';
const CACHE_TIMEOUT = 25200; // 7h
const BASE_URI = 'https://www.allocine.fr';
const URI = self::BASE_URI . '/film/sorties-semaine/';
const DESCRIPTION = 'Bridge for AlloCiné - Sorties cinéma cette semaine';
public function getName()
{
return self::NAME;
}
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI());
foreach ($html->find('section.section.section-wrap', 0)->find('li.mdl') as $element) {
$item = [];
$thumb = $element->find('figure.thumbnail', 0);
$meta = $element->find('div.meta-body', 0);
$synopsis = $element->find('div.synopsis', 0);
$title = $element->find('a[class*=meta-title-link]', 0);
$content = trim(defaultLinkTo($thumb->outertext . $meta->outertext . $synopsis->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);
$content = preg_replace('@data-src=@', 'src=', $content);
$item['content'] = $content;
$item['title'] = trim($title->innertext);
$item['uri'] = static::BASE_URI . '/' . substr($title->href, 1);
$this->items[] = $item;
}
}
}

View File

@@ -1,105 +1,95 @@
<?php
class AmazonBridge extends BridgeAbstract
{
const MAINTAINER = 'Alexis CHEMEL';
const NAME = 'Amazon';
const URI = 'https://www.amazon.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns products from Amazon search';
class AmazonBridge extends BridgeAbstract {
const PARAMETERS = [[
'q' => [
'name' => 'Keyword',
'required' => true,
'exampleValue' => 'watch',
],
'sort' => [
'name' => 'Sort by',
'type' => 'list',
'values' => [
'Relevance' => 'relevanceblender',
'Price: Low to High' => 'price-asc-rank',
'Price: High to Low' => 'price-desc-rank',
'Average Customer Review' => 'review-rank',
'Newest Arrivals' => 'date-desc-rank',
],
'defaultValue' => 'relevanceblender',
],
'tld' => [
'name' => 'Country',
'type' => 'list',
'values' => [
'Australia' => 'com.au',
'Brazil' => 'com.br',
'Canada' => 'ca',
'China' => 'cn',
'France' => 'fr',
'Germany' => 'de',
'India' => 'in',
'Italy' => 'it',
'Japan' => 'co.jp',
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Poland' => 'pl',
'Spain' => 'es',
'Sweden' => 'se',
'Turkey' => 'com.tr',
'United Kingdom' => 'co.uk',
'United States' => 'com',
],
'defaultValue' => 'com',
],
]];
const MAINTAINER = 'Alexis CHEMEL';
const NAME = 'Amazon';
const URI = 'https://www.amazon.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns products from Amazon search';
public function collectData()
{
$baseUrl = sprintf('https://www.amazon.%s', $this->getInput('tld'));
const PARAMETERS = array(array(
'q' => array(
'name' => 'Keyword',
'required' => true,
'exampleValue' => 'watch',
),
'sort' => array(
'name' => 'Sort by',
'type' => 'list',
'values' => array(
'Relevance' => 'relevanceblender',
'Price: Low to High' => 'price-asc-rank',
'Price: High to Low' => 'price-desc-rank',
'Average Customer Review' => 'review-rank',
'Newest Arrivals' => 'date-desc-rank',
),
'defaultValue' => 'relevanceblender',
),
'tld' => array(
'name' => 'Country',
'type' => 'list',
'values' => array(
'Australia' => 'com.au',
'Brazil' => 'com.br',
'Canada' => 'ca',
'China' => 'cn',
'France' => 'fr',
'Germany' => 'de',
'India' => 'in',
'Italy' => 'it',
'Japan' => 'co.jp',
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Spain' => 'es',
'United Kingdom' => 'co.uk',
'United States' => 'com',
),
'defaultValue' => 'com',
),
));
$url = sprintf(
'%s/s/?field-keywords=%s&sort=%s',
$baseUrl,
urlencode($this->getInput('q')),
$this->getInput('sort')
);
public function getName(){
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
}
$dom = getSimpleHTMLDOM($url);
return parent::getName();
}
$elements = $dom->find('div.s-result-item');
public function collectData() {
foreach ($elements as $element) {
$item = [];
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
$title = $element->find('h2', 0);
if (!$title) {
continue;
}
$html = getSimpleHTMLDOM($uri);
$item['title'] = $title->innertext;
foreach($html->find('li.s-result-item') as $element) {
$itemUrl = $element->find('a', 0)->href;
$item['uri'] = urljoin($baseUrl, $itemUrl);
$item = array();
$image = $element->find('img', 0);
if ($image) {
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />';
}
// Title
$title = $element->find('h2', 0);
if (is_null($title)) {
continue;
}
$price = $element->find('span.a-price > .a-offscreen', 0);
if ($price) {
$item['content'] .= $price->innertext;
}
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);
$this->items[] = $item;
}
}
// Url
$uri = $title->parent()->getAttribute('href');
$uri = substr($uri, 0, strrpos($uri, '/'));
public function getName()
{
if (!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
}
$item['uri'] = substr($uri, 0, strrpos($uri, '/'));
return parent::getName();
}
// 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;
}
}
}

View File

@@ -1,254 +1,214 @@
<?php
class AmazonPriceTrackerBridge extends BridgeAbstract
{
const MAINTAINER = 'captn3m0, sal0max';
const NAME = 'Amazon Price Tracker';
const URI = 'https://www.amazon.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Tracks price for a single product on Amazon';
class AmazonPriceTrackerBridge extends BridgeAbstract {
const MAINTAINER = 'captn3m0, sal0max';
const NAME = 'Amazon Price Tracker';
const URI = 'https://www.amazon.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Tracks price for a single product on Amazon';
const PARAMETERS = [
[
'asin' => [
'name' => 'ASIN',
'required' => true,
'exampleValue' => 'B071GB1VMQ',
// https://stackoverflow.com/a/12827734
'pattern' => 'B[\dA-Z]{9}|\d{9}(X|\d)',
],
'tld' => [
'name' => 'Country',
'type' => 'list',
'values' => [
'Australia' => 'com.au',
'Brazil' => 'com.br',
'Canada' => 'ca',
'China' => 'cn',
'France' => 'fr',
'Germany' => 'de',
'India' => 'in',
'Italy' => 'it',
'Japan' => 'co.jp',
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Poland' => 'pl',
'Spain' => 'es',
'Sweden' => 'se',
'Turkey' => 'com.tr',
'United Kingdom' => 'co.uk',
'United States' => 'com',
],
'defaultValue' => 'com',
],
]];
const PARAMETERS = array(
array(
'asin' => array(
'name' => 'ASIN',
'required' => true,
'exampleValue' => 'B071GB1VMQ',
// https://stackoverflow.com/a/12827734
'pattern' => 'B[\dA-Z]{9}|\d{9}(X|\d)',
),
'tld' => array(
'name' => 'Country',
'type' => 'list',
'values' => array(
'Australia' => 'com.au',
'Brazil' => 'com.br',
'Canada' => 'ca',
'China' => 'cn',
'France' => 'fr',
'Germany' => 'de',
'India' => 'in',
'Italy' => 'it',
'Japan' => 'co.jp',
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Spain' => 'es',
'Sweden' => 'se',
'United Kingdom' => 'co.uk',
'United States' => 'com',
),
'defaultValue' => 'com',
),
));
const PRICE_SELECTORS = [
'#priceblock_ourprice',
'.priceBlockBuyingPriceString',
'#newBuyBoxPrice',
'#tp_price_block_total_price_ww',
'span.offer-price',
'.a-color-price',
];
const PRICE_SELECTORS = array(
'#priceblock_ourprice',
'.priceBlockBuyingPriceString',
'#newBuyBoxPrice',
'#tp_price_block_total_price_ww',
'span.offer-price',
'.a-color-price',
);
const WHITESPACE = " \t\n\r\0\x0B\xC2\xA0";
protected $title;
protected $title;
/**
* Generates domain name given a amazon TLD
*/
private function getDomainName() {
return 'https://www.amazon.' . $this->getInput('tld');
}
/**
* Generates domain name given a amazon TLD
*/
private function getDomainName()
{
return 'https://www.amazon.' . $this->getInput('tld');
}
/**
* Generates URI for a Amazon product page
*/
public function getURI() {
if (!is_null($this->getInput('asin'))) {
return $this->getDomainName() . '/dp/' . $this->getInput('asin');
}
return parent::getURI();
}
/**
* Generates URI for a Amazon product page
*/
public function getURI()
{
if (!is_null($this->getInput('asin'))) {
return $this->getDomainName() . '/dp/' . $this->getInput('asin');
}
return parent::getURI();
}
/**
* Scrapes the product title from the html page
* returns the default title if scraping fails
*/
private function getTitle($html) {
$titleTag = $html->find('#productTitle', 0);
/**
* Scrapes the product title from the html page
* returns the default title if scraping fails
*/
private function getTitle($html)
{
$titleTag = $html->find('#productTitle', 0);
if (!$titleTag) {
return $this->getDefaultTitle();
} else {
return trim(html_entity_decode($titleTag->innertext, ENT_QUOTES));
}
}
if (!$titleTag) {
return $this->getDefaultTitle();
} else {
return trim(html_entity_decode($titleTag->innertext, ENT_QUOTES));
}
}
/**
* Title used by the feed if none could be found
*/
private function getDefaultTitle() {
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin');
}
/**
* Title used by the feed if none could be found
*/
private function getDefaultTitle()
{
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin');
}
/**
* Returns name for the feed
* Uses title (already scraped) if it has one
*/
public function getName() {
if (isset($this->title)) {
return $this->title;
} else {
return parent::getName();
}
}
/**
* Returns name for the feed
* Uses title (already scraped) if it has one
*/
public function getName()
{
if (isset($this->title)) {
return $this->title;
} else {
return parent::getName();
}
}
private function parseDynamicImage($attribute) {
$json = json_decode(html_entity_decode($attribute), true);
private function parseDynamicImage($attribute)
{
$json = json_decode(html_entity_decode($attribute), true);
if ($json and count($json) > 0) {
return array_keys($json)[0];
}
}
if ($json and count($json) > 0) {
return array_keys($json)[0];
}
}
/**
* Returns a generated image tag for the product
*/
private function getImage($html) {
$imageSrc = $html->find('#main-image-container img', 0);
/**
* Returns a generated image tag for the product
*/
private function getImage($html)
{
$imageSrc = $html->find('#main-image-container img', 0);
if ($imageSrc) {
$hiresImage = $imageSrc->getAttribute('data-old-hires');
$dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image');
$image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute);
}
$image = $image ?: 'https://placekitten.com/200/300';
if ($imageSrc) {
$hiresImage = $imageSrc->getAttribute('data-old-hires');
$dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image');
$image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute);
}
$image = $image ?: 'https://placekitten.com/200/300';
return <<<EOT
return <<<EOT
<img width="300" style="max-width:300;max-height:300" src="$image" alt="{$this->title}" />
EOT;
}
}
/**
* Return \simple_html_dom object
* for the entire html of the product page
*/
private function getHtml()
{
$uri = $this->getURI();
/**
* Return \simple_html_dom object
* for the entire html of the product page
*/
private function getHtml() {
$uri = $this->getURI();
return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.');
}
return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.');
}
private function scrapePriceFromMetrics($html)
{
$asinData = $html->find('#cerberus-data-metrics', 0);
private function scrapePriceFromMetrics($html) {
$asinData = $html->find('#cerberus-data-metrics', 0);
// <div id="cerberus-data-metrics" style="display: none;"
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
// data-asin-currency-code="USD" data-substitute-count="-1" ... />
if ($asinData) {
return [
'price' => $asinData->getAttribute('data-asin-price'),
'currency' => $asinData->getAttribute('data-asin-currency-code'),
'shipping' => $asinData->getAttribute('data-asin-shipping')
];
}
// <div id="cerberus-data-metrics" style="display: none;"
// data-asin="B00WTHJ5SU" data-asin-price="14.99" data-asin-shipping="0"
// data-asin-currency-code="USD" data-substitute-count="-1" ... />
if ($asinData) {
return array(
'price' => $asinData->getAttribute('data-asin-price'),
'currency' => $asinData->getAttribute('data-asin-currency-code'),
'shipping' => $asinData->getAttribute('data-asin-shipping')
);
}
return false;
}
return false;
}
private function scrapePriceTwister($html)
{
$str = $html->find('.twister-plus-buying-options-price-data', 0);
private function scrapePriceGeneric($html) {
$priceDiv = null;
$data = json_decode($str->innertext, true);
if (count($data) === 1) {
$data = $data[0];
return [
'displayPrice' => $data['displayPrice'],
'currency' => $data['currency'],
'shipping' => '0',
];
}
foreach(self::PRICE_SELECTORS as $sel) {
$priceDiv = $html->find($sel, 0);
if ($priceDiv) {
break;
}
}
return false;
}
if (!$priceDiv) {
return false;
}
private function scrapePriceGeneric($html)
{
$default = [
'price' => null,
'displayPrice' => null,
'currency' => null,
'shipping' => null,
];
$priceDiv = null;
$priceString = $priceDiv->plaintext;
foreach (self::PRICE_SELECTORS as $sel) {
$priceDiv = $html->find($sel, 0);
if ($priceDiv) {
break;
}
}
preg_match('/[\d.,]+/', $priceString, $matches);
if (!$priceDiv) {
return $default;
}
$price = $matches[0];
$currency = trim(str_replace($price, '', $priceString), " \t\n\r\0\x0B\xC2\xA0");
$priceString = str_replace(str_split(self::WHITESPACE), '', $priceDiv->plaintext);
preg_match('/(\d+\.\d{0,2})/', $priceString, $matches);
if ($price != null && $currency != null) {
return array(
'price' => $price,
'currency' => $currency,
'shipping' => '0'
);
}
$price = $matches[0] ?? null;
$currency = str_replace($price, '', $priceString);
return false;
}
if ($price != null && $currency != null) {
return [
'price' => $price,
'displayPrice' => null,
'currency' => $currency,
'shipping' => '0'
];
}
return $default;
}
/**
* Scrape method for Amazon product page
* @return [type] [description]
*/
public function collectData() {
$html = $this->getHtml();
$this->title = $this->getTitle($html);
$imageTag = $this->getImage($html);
public function collectData()
{
$html = $this->getHtml();
$this->title = $this->getTitle($html);
$image = $this->getImage($html);
$data = $this->scrapePriceGeneric($html);
$data = $this->scrapePriceFromMetrics($html) ?: $this->scrapePriceGeneric($html);
// render
$content = '';
$price = $data['displayPrice'];
if (!$price) {
$price = sprintf('%s %s', $data['price'], $data['currency']);
}
$content .= sprintf('%s<br>Price: %s', $image, $price);
if ($data['shipping'] !== '0') {
$content .= sprintf('<br>Shipping: %s %s</br>', $data['shipping'], $data['currency']);
}
$item = array(
'title' => $this->title,
'uri' => $this->getURI(),
'content' => "$imageTag<br/>Price: {$data['price']} {$data['currency']}",
// This is to ensure that feed readers notice the price change
'uid' => md5($data['price'])
);
$item = [
'title' => $this->title,
'uri' => $this->getURI(),
'content' => $content,
// 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;
}
$this->items[] = $item;
}
}

View File

@@ -1,218 +1,217 @@
<?php
class AnidexBridge extends BridgeAbstract {
class AnidexBridge extends BridgeAbstract
{
const MAINTAINER = 'ORelio';
const NAME = 'Anidex';
const URI = 'http://anidex.info/'; // anidex.info has ddos-guard so we need to use anidex.moe
const ALTERNATE_URI = 'https://anidex.moe/'; // anidex.moe returns 301 unless Host is set to anidex.info
const ALTERNATE_HOST = 'anidex.info'; // Correct host for requesting anidex.moe without 301 redirect
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
const PARAMETERS = [
[
'id' => [
'name' => 'Category',
'type' => 'list',
'values' => [
'All categories' => '0',
'Anime' => '1,2,3',
'Anime - Sub' => '1',
'Anime - Raw' => '2',
'Anime - Dub' => '3',
'Live Action' => '4,5',
'Live Action - Sub' => '4',
'Live Action - Raw' => '5',
'Light Novel' => '6',
'Manga' => '7,8',
'Manga - Translated' => '7',
'Manga - Raw' => '8',
'Music' => '9,10,11',
'Music - Lossy' => '9',
'Music - Lossless' => '10',
'Music - Video' => '11',
'Games' => '12',
'Applications' => '13',
'Pictures' => '14',
'Adult Video' => '15',
'Other' => '16'
]
],
'lang_id' => [
'name' => 'Language',
'type' => 'list',
'values' => [
'All languages' => '0',
'English' => '1',
'Japanese' => '2',
'Polish' => '3',
'Serbo-Croatian' => '4',
'Dutch' => '5',
'Italian' => '6',
'Russian' => '7',
'German' => '8',
'Hungarian' => '9',
'French' => '10',
'Finnish' => '11',
'Vietnamese' => '12',
'Greek' => '13',
'Bulgarian' => '14',
'Spanish (Spain)' => '15',
'Portuguese (Brazil)' => '16',
'Portuguese (Portugal)' => '17',
'Swedish' => '18',
'Arabic' => '19',
'Danish' => '20',
'Chinese (Simplified)' => '21',
'Bengali' => '22',
'Romanian' => '23',
'Czech' => '24',
'Mongolian' => '25',
'Turkish' => '26',
'Indonesian' => '27',
'Korean' => '28',
'Spanish (LATAM)' => '29',
'Persian' => '30',
'Malaysian' => '31'
]
],
'group_id' => [
'name' => 'Group ID',
'type' => 'number'
],
'r' => [
'name' => 'Hide Remakes',
'type' => 'checkbox'
],
'b' => [
'name' => 'Only Batches',
'type' => 'checkbox'
],
'a' => [
'name' => 'Only Authorized',
'type' => 'checkbox'
],
'q' => [
'name' => 'Keyword',
'description' => 'Keyword(s)',
'type' => 'text'
],
'h' => [
'name' => 'Adult content',
'type' => 'list',
'values' => [
'No filter' => '0',
'Hide +18' => '1',
'Only +18' => '2'
]
]
]
];
const MAINTAINER = 'ORelio';
const NAME = 'Anidex';
const URI = 'http://anidex.info/'; // anidex.info has ddos-guard so we need to use anidex.moe
const ALTERNATE_URI = 'https://anidex.moe/'; // anidex.moe returns 301 unless Host is set to anidex.info
const ALTERNATE_HOST = 'anidex.info'; // Correct host for requesting anidex.moe without 301 redirect
const DESCRIPTION = 'Returns the newest torrents, with optional search criteria.';
const PARAMETERS = array(
array(
'id' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All categories' => '0',
'Anime' => '1,2,3',
'Anime - Sub' => '1',
'Anime - Raw' => '2',
'Anime - Dub' => '3',
'Live Action' => '4,5',
'Live Action - Sub' => '4',
'Live Action - Raw' => '5',
'Light Novel' => '6',
'Manga' => '7,8',
'Manga - Translated' => '7',
'Manga - Raw' => '8',
'Music' => '9,10,11',
'Music - Lossy' => '9',
'Music - Lossless' => '10',
'Music - Video' => '11',
'Games' => '12',
'Applications' => '13',
'Pictures' => '14',
'Adult Video' => '15',
'Other' => '16'
)
),
'lang_id' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'All languages' => '0',
'English' => '1',
'Japanese' => '2',
'Polish' => '3',
'Serbo-Croatian' => '4',
'Dutch' => '5',
'Italian' => '6',
'Russian' => '7',
'German' => '8',
'Hungarian' => '9',
'French' => '10',
'Finnish' => '11',
'Vietnamese' => '12',
'Greek' => '13',
'Bulgarian' => '14',
'Spanish (Spain)' => '15',
'Portuguese (Brazil)' => '16',
'Portuguese (Portugal)' => '17',
'Swedish' => '18',
'Arabic' => '19',
'Danish' => '20',
'Chinese (Simplified)' => '21',
'Bengali' => '22',
'Romanian' => '23',
'Czech' => '24',
'Mongolian' => '25',
'Turkish' => '26',
'Indonesian' => '27',
'Korean' => '28',
'Spanish (LATAM)' => '29',
'Persian' => '30',
'Malaysian' => '31'
)
),
'group_id' => array(
'name' => 'Group ID',
'type' => 'number'
),
'r' => array(
'name' => 'Hide Remakes',
'type' => 'checkbox'
),
'b' => array(
'name' => 'Only Batches',
'type' => 'checkbox'
),
'a' => array(
'name' => 'Only Authorized',
'type' => 'checkbox'
),
'q' => array(
'name' => 'Keyword',
'description' => 'Keyword(s)',
'type' => 'text'
),
'h' => array(
'name' => 'Adult content',
'type' => 'list',
'values' => array(
'No filter' => '0',
'Hide +18' => '1',
'Only +18' => '2'
)
)
)
);
public function collectData()
{
// Build Search URL from user-provided parameters
$search_url = self::ALTERNATE_URI . '?s=upload_timestamp&o=desc';
foreach (['id', 'lang_id', 'group_id'] as $param_name) {
$param = $this->getInput($param_name);
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
$search_url .= '&' . $param_name . '=' . $param;
}
}
foreach (['r', 'b', 'a'] as $param_name) {
$param = $this->getInput($param_name);
if (!empty($param) && boolval($param)) {
$search_url .= '&' . $param_name . '=1';
}
}
$query = $this->getInput('q');
if (!empty($query)) {
$search_url .= '&q=' . urlencode($query);
}
$opt = [];
$h = $this->getInput('h');
if (!empty($h) && intval($h) != 0 && ctype_digit($h)) {
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
}
public function collectData() {
// We need to use a different Host HTTP header to reach the correct page on ALTERNATE_URI
$headers = ['Host: ' . self::ALTERNATE_HOST];
// Build Search URL from user-provided parameters
$search_url = self::ALTERNATE_URI . '?s=upload_timestamp&o=desc';
foreach (array('id', 'lang_id', 'group_id') as $param_name) {
$param = $this->getInput($param_name);
if (!empty($param) && intval($param) != 0 && ctype_digit(str_replace(',', '', $param))) {
$search_url .= '&' . $param_name . '=' . $param;
}
}
foreach (array('r', 'b', 'a') as $param_name) {
$param = $this->getInput($param_name);
if (!empty($param) && boolval($param)) {
$search_url .= '&' . $param_name . '=1';
}
}
$query = $this->getInput('q');
if (!empty($query)) {
$search_url .= '&q=' . urlencode($query);
}
$opt = array();
$h = $this->getInput('h');
if (!empty($h) && intval($h) != 0 && ctype_digit($h)) {
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
}
// The HTTPS certificate presented by anidex.moe is for anidex.info. We need to ignore this.
// As a consequence, the bridge is intentionally marked as insecure by setting self::URI to http://
$opt[CURLOPT_SSL_VERIFYHOST] = 0;
$opt[CURLOPT_SSL_VERIFYPEER] = 0;
// We need to use a different Host HTTP header to reach the correct page on ALTERNATE_URI
$headers = array('Host: ' . self::ALTERNATE_HOST);
// Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url, $headers, $opt);
$links = $html->find('a');
$results = [];
foreach ($links as $link) {
if (strpos($link->href, '/torrent/') === 0 && !in_array($link->href, $results)) {
$results[] = $link->href;
}
}
if (empty($results) && empty($this->getInput('q'))) {
returnServerError('No results from Anidex: ' . $search_url);
}
// The HTTPS certificate presented by anidex.moe is for anidex.info. We need to ignore this.
// As a consequence, the bridge is intentionally marked as insecure by setting self::URI to http://
$opt[CURLOPT_SSL_VERIFYHOST] = 0;
$opt[CURLOPT_SSL_VERIFYPEER] = 0;
//Process each item individually
foreach ($results as $element) {
//Limit total amount of requests
if (count($this->items) >= 20) {
break;
}
// Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url, $headers, $opt);
$links = $html->find('a');
$results = array();
foreach ($links as $link)
if (strpos($link->href, '/torrent/') === 0 && !in_array($link->href, $results))
$results[] = $link->href;
if (empty($results) && empty($this->getInput('q')))
returnServerError('No results from Anidex: ' . $search_url);
$torrent_id = str_replace('/torrent/', '', $element);
//Process each item individually
foreach ($results as $element) {
//Ignore entries without valid torrent ID
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
//Retrieve data for this torrent ID
$item_browse_uri = self::URI . 'torrent/' . $torrent_id;
$item_fetch_uri = self::ALTERNATE_URI . 'torrent/' . $torrent_id;
//Limit total amount of requests
if(count($this->items) >= 20) {
break;
}
//Retrieve full description from torrent page (cached for 24 hours: 86400 seconds)
if ($item_html = getSimpleHTMLDOMCached($item_fetch_uri, 86400, $headers, $opt)) {
//Retrieve data from page contents
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
$item_desc = $item_html->find('div.panel-body', 0);
$item_author = trim($item_html->find('span.fa-user', 0)->parent()->plaintext);
$item_date = strtotime(trim($item_html->find('span.fa-clock', 0)->parent()->plaintext));
$item_image = $this->getURI() . 'images/user_logos/default.png';
$torrent_id = str_replace('/torrent/', '', $element);
//Check for description-less torrent andn optionally extract image
$desc_title_found = false;
foreach ($item_html->find('h3.panel-title') as $h3) {
if (strpos($h3, 'Description') !== false) {
$desc_title_found = true;
break;
}
}
if ($desc_title_found) {
//Retrieve image for thumbnail or generic logo fallback
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
}
$item_desc = trim($item_desc->innertext);
} else {
$item_desc = '<em>No description.</em>';
}
//Ignore entries without valid torrent ID
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
//Build and add final item
$item = [];
$item['uri'] = $item_browse_uri;
$item['title'] = $item_title;
$item['author'] = $item_author;
$item['timestamp'] = $item_date;
$item['enclosures'] = [$item_image];
$item['content'] = $item_desc;
$this->items[] = $item;
}
}
$element = null;
}
$results = null;
}
//Retrieve data for this torrent ID
$item_browse_uri = self::URI . 'torrent/' . $torrent_id;
$item_fetch_uri = self::ALTERNATE_URI . 'torrent/' . $torrent_id;
//Retrieve full description from torrent page (cached for 24 hours: 86400 seconds)
if ($item_html = getSimpleHTMLDOMCached($item_fetch_uri, 86400, $headers, $opt)) {
//Retrieve data from page contents
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
$item_desc = $item_html->find('div.panel-body', 0);
$item_author = trim($item_html->find('span.fa-user', 0)->parent()->plaintext);
$item_date = strtotime(trim($item_html->find('span.fa-clock', 0)->parent()->plaintext));
$item_image = $this->getURI() . 'images/user_logos/default.png';
//Check for description-less torrent andn optionally extract image
$desc_title_found = false;
foreach ($item_html->find('h3.panel-title') as $h3) {
if (strpos($h3, 'Description') !== false) {
$desc_title_found = true;
break;
}
}
if ($desc_title_found) {
//Retrieve image for thumbnail or generic logo fallback
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
}
$item_desc = trim($item_desc->innertext);
} else {
$item_desc = '<em>No description.</em>';
}
//Build and add final item
$item = array();
$item['uri'] = $item_browse_uri;
$item['title'] = $item_title;
$item['author'] = $item_author;
$item['timestamp'] = $item_date;
$item['enclosures'] = array($item_image);
$item['content'] = $item_desc;
$this->items[] = $item;
}
}
$element = null;
}
$results = null;
}
}

View File

@@ -1,133 +1,137 @@
<?php
class AnimeUltimeBridge extends BridgeAbstract {
class AnimeUltimeBridge extends BridgeAbstract
{
const MAINTAINER = 'ORelio';
const NAME = 'Anime-Ultime';
const URI = 'http://www.anime-ultime.net/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the newest releases posted on Anime-Ultime.';
const PARAMETERS = [ [
'type' => [
'name' => 'Type',
'type' => 'list',
'values' => [
'Everything' => '',
'Anime' => 'A',
'Drama' => 'D',
'Tokusatsu' => 'T'
]
]
]];
const MAINTAINER = 'ORelio';
const NAME = 'Anime-Ultime';
const URI = 'http://www.anime-ultime.net/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the newest releases posted on Anime-Ultime.';
const PARAMETERS = array( array(
'type' => array(
'name' => 'Type',
'type' => 'list',
'values' => array(
'Everything' => '',
'Anime' => 'A',
'Drama' => 'D',
'Tokusatsu' => 'T'
)
)
));
private $filter = 'Releases';
private $filter = 'Releases';
public function collectData()
{
//Add type filter if provided
$typeFilter = $this->getKey('type');
public function collectData(){
//Build date and filters for making requests
$thismonth = date('mY') . $typeFilter;
$lastmonth = date('mY', mktime(0, 0, 0, date('n') - 1, 1, date('Y'))) . $typeFilter;
//Add type filter if provided
$typeFilter = array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
//Process each HTML page until having 10 releases
$processedOK = 0;
foreach ([$thismonth, $lastmonth] as $requestFilter) {
$url = self::URI . 'history-0-1/' . $requestFilter;
$html = getContents($url);
// Convert html from iso-8859-1 => utf8
$html = utf8_encode($html);
$html = str_get_html($html);
//Build date and filters for making requests
$thismonth = date('mY') . $typeFilter;
$lastmonth = date('mY', mktime(0, 0, 0, date('n') - 1, 1, date('Y'))) . $typeFilter;
//Relases are sorted by day : process each day individually
foreach ($html->find('div.history', 0)->find('h3') as $daySection) {
//Retrieve day and build date information
$dateString = $daySection->plaintext;
$day = intval(substr($dateString, strpos($dateString, ' ') + 1, 2));
$item_date = strtotime(str_pad($day, 2, '0', STR_PAD_LEFT)
. '-'
. substr($requestFilter, 0, 2)
. '-'
. substr($requestFilter, 2, 4));
//Process each HTML page until having 10 releases
$processedOK = 0;
foreach (array($thismonth, $lastmonth) as $requestFilter) {
//<h3>day</h3><br /><table><tr> <-- useful data in table rows
$release = $daySection->next_sibling()->next_sibling()->first_child();
//Retrive page contents
$url = self::URI . 'history-0-1/' . $requestFilter;
$html = getSimpleHTMLDOM($url);
//Process each release of that day, ignoring first table row: contains table headers
while (!is_null($release = $release->next_sibling())) {
if (count($release->find('td')) > 0) {
//Retrieve metadata from table columns
$item_link_element = $release->find('td', 0)->find('a', 0);
$item_uri = self::URI . $item_link_element->href;
$item_name = html_entity_decode($item_link_element->plaintext);
//Relases are sorted by day : process each day individually
foreach($html->find('div.history', 0)->find('h3') as $daySection) {
$item_image = self::URI . substr(
$item_link_element->onmouseover,
37,
strpos($item_link_element->onmouseover, ' ', 37) - 37
);
//Retrieve day and build date information
$dateString = $daySection->plaintext;
$day = intval(substr($dateString, strpos($dateString, ' ') + 1, 2));
$item_date = strtotime(str_pad($day, 2, '0', STR_PAD_LEFT)
. '-'
. substr($requestFilter, 0, 2)
. '-'
. substr($requestFilter, 2, 4));
$item_episode = html_entity_decode(
str_pad(
$release->find('td', 1)->plaintext,
2,
'0',
STR_PAD_LEFT
)
);
//<h3>day</h3><br /><table><tr> <-- useful data in table rows
$release = $daySection->next_sibling()->next_sibling()->first_child();
$item_fansub = $release->find('td', 2)->plaintext;
$item_type = $release->find('td', 4)->plaintext;
//Process each release of that day, ignoring first table row: contains table headers
while(!is_null($release = $release->next_sibling())) {
if(count($release->find('td')) > 0) {
if (!empty($item_uri)) {
// 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
);
$item_description = substr(
$item_description,
0,
strpos($item_description, '<div id="table">')
);
//Retrieve metadata from table columns
$item_link_element = $release->find('td', 0)->find('a', 0);
$item_uri = self::URI . $item_link_element->href;
$item_name = html_entity_decode($item_link_element->plaintext);
// Convert relative image src into absolute image src, remove line breaks
$item_description = defaultLinkTo($item_description, self::URI);
$item_description = str_replace("\r", '', $item_description);
$item_description = str_replace("\n", '', $item_description);
$item_image = self::URI . substr(
$item_link_element->onmouseover,
37,
strpos($item_link_element->onmouseover, ' ', 37) - 37
);
//Build and add final item
$item = [];
$item['uri'] = $item_uri;
$item['title'] = $item_name . ' ' . $item_type . ' ' . $item_episode;
$item['author'] = $item_fansub;
$item['timestamp'] = $item_date;
$item['enclosures'] = [$item_image];
$item['content'] = $item_description;
$this->items[] = $item;
$processedOK++;
$item_episode = html_entity_decode(
str_pad(
$release->find('td', 1)->plaintext,
2,
'0',
STR_PAD_LEFT
)
);
//Stop processing once limit is reached
if ($processedOK >= 10) {
return;
}
}
}
}
}
}
}
$item_fansub = $release->find('td', 2)->plaintext;
$item_type = $release->find('td', 4)->plaintext;
public function getName()
{
if (!is_null($this->getInput('type'))) {
return 'Latest ' . $this->getKey('type') . ' - Anime-Ultime Bridge';
}
if(!empty($item_uri)) {
return parent::getName();
}
// Retrieve description from description page
$html_item = getContents($item_uri);
$item_description = substr(
$html_item,
strpos($html_item, 'class="principal_contain" align="center">') + 41
);
$item_description = substr($item_description,
0,
strpos($item_description, '<div id="table">')
);
// Convert relative image src into absolute image src, remove line breaks
$item_description = defaultLinkTo($item_description, self::URI);
$item_description = str_replace("\r", '', $item_description);
$item_description = str_replace("\n", '', $item_description);
//Build and add final item
$item = array();
$item['uri'] = $item_uri;
$item['title'] = $item_name . ' ' . $item_type . ' ' . $item_episode;
$item['author'] = $item_fansub;
$item['timestamp'] = $item_date;
$item['enclosures'] = array($item_image);
$item['content'] = $item_description;
$this->items[] = $item;
$processedOK++;
//Stop processing once limit is reached
if ($processedOK >= 10)
return;
}
}
}
}
}
}
public function getName() {
if(!is_null($this->getInput('type'))) {
$typeFilter = array_search(
$this->getInput('type'),
self::PARAMETERS[$this->queriedContext]['type']['values']
);
return 'Latest ' . $typeFilter . ' - Anime-Ultime Bridge';
}
return parent::getName();
}
}

View File

@@ -1,171 +1,150 @@
<?php
class AppleAppStoreBridge extends BridgeAbstract
{
const MAINTAINER = 'captn3m0';
const NAME = 'Apple App Store';
const URI = 'https://apps.apple.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns version updates for a specific application';
class AppleAppStoreBridge extends BridgeAbstract {
const PARAMETERS = [[
'id' => [
'name' => 'Application ID',
'required' => true,
'exampleValue' => '310633997'
],
'p' => [
'name' => 'Platform',
'type' => 'list',
'values' => [
'iPad' => 'ipad',
'iPhone' => 'iphone',
'Mac' => 'mac',
const MAINTAINER = 'captn3m0';
const NAME = 'Apple App Store';
const URI = 'https://apps.apple.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns version updates for a specific application';
// The following 2 are present in responses
// but not yet tested
'Web' => 'web',
'Apple TV' => 'appletv',
],
'defaultValue' => 'iphone',
],
'country' => [
'name' => 'Store Country',
'type' => 'list',
'values' => [
'US' => 'US',
'India' => 'IN',
'Canada' => 'CA',
'Germany' => 'DE',
'Netherlands' => 'NL',
'Belgium (NL)' => 'BENL',
'Belgium (FR)' => 'BEFR',
'France' => 'FR',
'Italy' => 'IT',
'United Kingdom' => 'UK',
'Spain' => 'ES',
'Portugal' => 'PT',
'Australia' => 'AU',
'New Zealand' => 'NZ',
'Indonesia' => 'ID',
'Brazil' => 'BR',
],
'defaultValue' => 'US',
],
]];
const PARAMETERS = array(array(
'id' => array(
'name' => 'Application ID',
'required' => true,
'exampleValue' => '310633997'
),
'p' => array(
'name' => 'Platform',
'type' => 'list',
'values' => array(
'iPad' => 'ipad',
'iPhone' => 'iphone',
'Mac' => 'mac',
const PLATFORM_MAPPING = [
'iphone' => 'ios',
'ipad' => 'ios',
];
// The following 2 are present in responses
// but not yet tested
'Web' => 'web',
'Apple TV' => 'appletv',
),
'defaultValue' => 'iphone',
),
'country' => array(
'name' => 'Store Country',
'type' => 'list',
'values' => array(
'US' => 'US',
'India' => 'IN',
'Canada' => 'CA',
'Germany' => 'DE',
),
'defaultValue' => 'US',
),
));
private function makeHtmlUrl($id, $country)
{
return 'https://apps.apple.com/' . $country . '/app/id' . $id;
}
const PLATFORM_MAPPING = array(
'iphone' => 'ios',
'ipad' => 'ios',
);
private function makeJsonUrl($id, $platform, $country)
{
return "https://amp-api.apps.apple.com/v1/catalog/$country/apps/$id?platform=$platform&extend=versionHistory";
}
private function makeHtmlUrl($id, $country){
return 'https://apps.apple.com/' . $country . '/app/id' . $id;
}
public function getName()
{
if (isset($this->name)) {
return $this->name . ' - AppStore Updates';
}
private function makeJsonUrl($id, $platform, $country){
return "https://amp-api.apps.apple.com/v1/catalog/$country/apps/$id?platform=$platform&extend=versionHistory";
}
return parent::getName();
}
public function getName(){
if (isset($this->name)) {
return $this->name . ' - AppStore Updates';
}
/**
* In case of some platforms, the data is present in the initial response
*/
private function getDataFromShoebox($id, $platform, $country)
{
$uri = $this->makeHtmlUrl($id, $country);
$html = getSimpleHTMLDOMCached($uri, 3600);
$script = $html->find('script[id="shoebox-ember-data-store"]', 0);
return parent::getName();
}
$json = json_decode($script->innertext, true);
return $json['data'];
}
/**
* In case of some platforms, the data is present in the initial response
*/
private function getDataFromShoebox($id, $platform, $country){
$uri = $this->makeHtmlUrl($id, $country);
$html = getSimpleHTMLDOMCached($uri, 3600);
$script = $html->find('script[id="shoebox-ember-data-store"]', 0);
private function getJWTToken($id, $platform, $country)
{
$uri = $this->makeHtmlUrl($id, $country);
$json = json_decode($script->innertext, true);
return $json['data'];
}
$html = getSimpleHTMLDOMCached($uri, 3600);
private function getJWTToken($id, $platform, $country){
$uri = $this->makeHtmlUrl($id, $country);
$meta = $html->find('meta[name="web-experience-app/config/environment"]', 0);
$html = getSimpleHTMLDOMCached($uri, 3600);
$json = urldecode($meta->content);
$meta = $html->find('meta[name="web-experience-app/config/environment"]', 0);
$json = json_decode($json);
$json = urldecode($meta->content);
return $json->MEDIA_API->token;
}
$json = json_decode($json);
private function getAppData($id, $platform, $country, $token)
{
$uri = $this->makeJsonUrl($id, $platform, $country);
return $json->MEDIA_API->token;
}
$headers = [
"Authorization: Bearer $token",
'Origin: https://apps.apple.com',
];
private function getAppData($id, $platform, $country, $token){
$uri = $this->makeJsonUrl($id, $platform, $country);
$json = json_decode(getContents($uri, $headers), true);
$headers = array(
"Authorization: Bearer $token",
);
return $json['data'][0];
}
$json = json_decode(getContents($uri, $headers), true);
/**
* Parses the version history from the data received
* @return array list of versions with details on each element
*/
private function getVersionHistory($data, $platform)
{
switch ($platform) {
case 'mac':
return $data['relationships']['platforms']['data'][0]['attributes']['versionHistory'];
default:
$os = self::PLATFORM_MAPPING[$platform];
return $data['attributes']['platformAttributes'][$os]['versionHistory'];
}
}
return $json['data'][0];
}
public function collectData()
{
$id = $this->getInput('id');
$country = $this->getInput('country');
$platform = $this->getInput('p');
/**
* Parses the version history from the data received
* @return array list of versions with details on each element
*/
private function getVersionHistory($data, $platform){
switch($platform) {
case 'mac':
return $data['relationships']['platforms']['data'][0]['attributes']['versionHistory'];
default:
$os = self::PLATFORM_MAPPING[$platform];
return $data['attributes']['platformAttributes'][$os]['versionHistory'];
}
}
switch ($platform) {
case 'mac':
$data = $this->getDataFromShoebox($id, $platform, $country);
break;
public function collectData() {
$id = $this->getInput('id');
$country = $this->getInput('country');
$platform = $this->getInput('p');
default:
$token = $this->getJWTToken($id, $platform, $country);
$data = $this->getAppData($id, $platform, $country, $token);
}
switch ($platform) {
case 'mac':
$data = $this->getDataFromShoebox($id, $platform, $country);
break;
$versionHistory = $this->getVersionHistory($data, $platform);
$name = $this->name = $data['attributes']['name'];
$author = $data['attributes']['artistName'];
default:
$token = $this->getJWTToken($id, $platform, $country);
$data = $this->getAppData($id, $platform, $country, $token);
}
foreach ($versionHistory as $row) {
$item = [];
$versionHistory = $this->getVersionHistory($data, $platform);
$name = $this->name = $data['attributes']['name'];
$author = $data['attributes']['artistName'];
$item['content'] = nl2br($row['releaseNotes']);
$item['title'] = $name . ' - ' . $row['versionDisplay'];
$item['timestamp'] = $row['releaseDate'];
$item['author'] = $author;
foreach ($versionHistory as $row) {
$item = array();
$item['uri'] = $this->makeHtmlUrl($id, $country);
$item['content'] = nl2br($row['releaseNotes']);
$item['title'] = $name . ' - ' . $row['versionDisplay'];
$item['timestamp'] = $row['releaseDate'];
$item['author'] = $author;
$this->items[] = $item;
}
}
$item['uri'] = $this->makeHtmlUrl($id, $country);
$this->items[] = $item;
}
}
}

View File

@@ -1,57 +1,55 @@
<?php
class AppleMusicBridge extends BridgeAbstract
{
const NAME = 'Apple Music';
const URI = 'https://www.apple.com';
const DESCRIPTION = 'Fetches the latest releases from an artist';
const MAINTAINER = 'bockiii';
const PARAMETERS = [[
'artist' => [
'name' => 'Artist ID',
'exampleValue' => '909253',
'required' => true,
],
'limit' => [
'name' => 'Latest X Releases (max 50)',
'defaultValue' => '10',
'required' => true,
],
]];
const CACHE_TIMEOUT = 21600; // 6 hours
class AppleMusicBridge extends BridgeAbstract {
const NAME = 'Apple Music';
const URI = 'https://www.apple.com';
const DESCRIPTION = 'Fetches the latest releases from an artist';
const MAINTAINER = 'bockiii';
const PARAMETERS = array(array(
'artist' => array(
'name' => 'Artist ID',
'exampleValue' => '909253',
'required' => true,
),
'limit' => array(
'name' => 'Latest X Releases (max 50)',
'defaultValue' => '10',
'required' => true,
),
));
const CACHE_TIMEOUT = 21600; // 6 hours
public function collectData()
{
# Limit the amount of releases to 50
if ($this->getInput('limit') > 50) {
$limit = 50;
} else {
$limit = $this->getInput('limit');
}
public function collectData() {
# Limit the amount of releases to 50
if ($this->getInput('limit') > 50) {
$limit = 50;
} else {
$limit = $this->getInput('limit');
}
$url = 'https://itunes.apple.com/lookup?id='
. $this->getInput('artist')
. '&entity=album&limit='
. $limit .
'&sort=recent';
$html = getSimpleHTMLDOM($url);
$url = 'https://itunes.apple.com/lookup?id='
. $this->getInput('artist')
. '&entity=album&limit='
. $limit .
'&sort=recent';
$html = getSimpleHTMLDOM($url);
$json = json_decode($html);
$json = json_decode($html);
foreach ($json->results as $obj) {
if ($obj->wrapperType === 'collection') {
$this->items[] = [
'title' => $obj->artistName . ' - ' . $obj->collectionName,
'uri' => $obj->collectionViewUrl,
'timestamp' => $obj->releaseDate,
'enclosures' => $obj->artworkUrl100,
'content' => '<a href=' . $obj->collectionViewUrl
. '><img src="' . $obj->artworkUrl100 . '" /></a><br><br>'
. $obj->artistName . ' - ' . $obj->collectionName
. '<br>'
. $obj->copyright,
];
}
}
}
foreach ($json->results as $obj) {
if ($obj->wrapperType === 'collection') {
$this->items[] = array(
'title' => $obj->artistName . ' - ' . $obj->collectionName,
'uri' => $obj->collectionViewUrl,
'timestamp' => $obj->releaseDate,
'enclosures' => $obj->artworkUrl100,
'content' => '<a href=' . $obj->collectionViewUrl
. '><img src="' . $obj->artworkUrl100 . '" /></a><br><br>'
. $obj->artistName . ' - ' . $obj->collectionName
. '<br>'
. $obj->copyright,
);
}
}
}
}

View File

@@ -1,101 +1,92 @@
<?php
class ArtStationBridge extends BridgeAbstract {
const NAME = 'ArtStation';
const URI = 'https://www.artstation.com';
const DESCRIPTION = 'Fetches the latest ten artworks from a search query on ArtStation.';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
class ArtStationBridge extends BridgeAbstract
{
const NAME = 'ArtStation';
const URI = 'https://www.artstation.com';
const DESCRIPTION = 'Fetches the latest ten artworks from a search query on ArtStation.';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
const PARAMETERS = array(
'Search Query' => array(
'q' => array(
'name' => 'Search term',
'required' => true,
'exampleValue' => 'bird'
)
)
);
const PARAMETERS = [
'Search Query' => [
'q' => [
'name' => 'Search term',
'required' => true,
'exampleValue' => 'bird'
]
]
];
public function getIcon() {
return 'https://www.artstation.com/assets/favicon-58653022bc38c1905ac7aa1b10bffa6b.ico';
}
public function getIcon()
{
return 'https://www.artstation.com/assets/favicon-58653022bc38c1905ac7aa1b10bffa6b.ico';
}
public function getName() {
return self::NAME . ': ' . $this->getInput('q');
}
public function getName()
{
return self::NAME . ': ' . $this->getInput('q');
}
private function fetchSearch($searchQuery) {
$data = '{"query":"' . $searchQuery . '","page":1,"per_page":50,"sorting":"date",';
$data .= '"pro_first":"1","filters":[],"additional_fields":[]}';
private function fetchSearch($searchQuery)
{
$data = '{"query":"' . $searchQuery . '","page":1,"per_page":50,"sorting":"date",';
$data .= '"pro_first":"1","filters":[],"additional_fields":[]}';
$header = array(
'Content-Type: application/json',
'Accept: application/json'
);
$header = [
'Content-Type: application/json',
'Accept: application/json'
];
$opts = array(
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true
);
$opts = [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $data,
CURLOPT_RETURNTRANSFER => true
];
$jsonSearchURL = self::URI . '/api/v2/search/projects.json';
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts);
return json_decode($jsonSearchStr);
}
$jsonSearchURL = self::URI . '/api/v2/search/projects.json';
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts);
return json_decode($jsonSearchStr);
}
private function fetchProject($hashID) {
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
$jsonProjectStr = getContents($jsonProjectURL);
return json_decode($jsonProjectStr);
}
private function fetchProject($hashID)
{
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
$jsonProjectStr = getContents($jsonProjectURL);
return json_decode($jsonProjectStr);
}
public function collectData() {
$searchTerm = $this->getInput('q');
$jsonQuery = $this->fetchSearch($searchTerm);
public function collectData()
{
$searchTerm = $this->getInput('q');
$jsonQuery = $this->fetchSearch($searchTerm);
foreach($jsonQuery->data as $media) {
// get detailed info about media item
$jsonProject = $this->fetchProject($media->hash_id);
foreach ($jsonQuery->data as $media) {
// get detailed info about media item
$jsonProject = $this->fetchProject($media->hash_id);
// create item
$item = array();
$item['title'] = $media->title;
$item['uri'] = $media->url;
$item['timestamp'] = strtotime($jsonProject->published_at);
$item['author'] = $media->user->full_name;
$item['categories'] = implode(',', $jsonProject->tags);
// create item
$item = [];
$item['title'] = $media->title;
$item['uri'] = $media->url;
$item['timestamp'] = strtotime($jsonProject->published_at);
$item['author'] = $media->user->full_name;
$item['categories'] = implode(',', $jsonProject->tags);
$item['content'] = '<a href="'
. $media->url
. '"><img style="max-width: 100%" src="'
. $jsonProject->cover_url
. '"></a><p>'
. $jsonProject->description
. '</p>';
$item['content'] = '<a href="'
. $media->url
. '"><img style="max-width: 100%" src="'
. $jsonProject->cover_url
. '"></a><p>'
. $jsonProject->description
. '</p>';
$numAssets = count($jsonProject->assets);
$numAssets = count($jsonProject->assets);
if ($numAssets > 1)
$item['content'] .= '<p><a href="'
. $media->url
. '">Project contains '
. ($numAssets - 1)
. ' more item(s).</a></p>';
if ($numAssets > 1) {
$item['content'] .= '<p><a href="'
. $media->url
. '">Project contains '
. ($numAssets - 1)
. ' more item(s).</a></p>';
}
$this->items[] = $item;
$this->items[] = $item;
if (count($this->items) >= 10) {
break;
}
}
}
if (count($this->items) >= 10)
break;
}
}
}

View File

@@ -1,162 +1,124 @@
<?php
class Arte7Bridge extends BridgeAbstract {
class Arte7Bridge extends BridgeAbstract
{
const NAME = 'Arte +7';
const URI = 'https://www.arte.tv/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns newest videos from ARTE +7';
// const MAINTAINER = 'mitsukarenai';
const NAME = 'Arte +7';
const URI = 'https://www.arte.tv/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns newest videos from ARTE +7';
const API_TOKEN = 'Nzc1Yjc1ZjJkYjk1NWFhN2I2MWEwMmRlMzAzNjI5NmU3NWU3ODg4ODJjOWMxNTMxYzEzZGRjYjg2ZGE4MmIwOA';
const API_TOKEN = 'Nzc1Yjc1ZjJkYjk1NWFhN2I2MWEwMmRlMzAzNjI5NmU3NWU3ODg4ODJjOWMxNTMxYzEzZGRjYjg2ZGE4MmIwOA';
const PARAMETERS = [
'global' => [
'sort_by' => [
'type' => 'list',
'name' => 'Sort by',
'required' => false,
'defaultValue' => null,
'values' => [
'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' => [
'type' => 'list',
'name' => 'Sort direction',
'required' => false,
'defaultValue' => 'DESC',
'values' => [
'Ascending' => 'ASC',
'Descending' => 'DESC'
],
],
'exclude_trailers' => [
'name' => 'Exclude trailers',
'type' => 'checkbox',
'required' => false,
'defaultValue' => false
],
],
'Category' => [
'lang' => [
'type' => 'list',
'name' => 'Language',
'values' => [
'Français' => 'fr',
'Deutsch' => 'de',
'English' => 'en',
'Español' => 'es',
'Polski' => 'pl',
'Italiano' => 'it'
],
],
'cat' => [
'type' => 'list',
'name' => 'Category',
'values' => [
'All videos' => null,
'News & society' => 'ACT',
'Series & fiction' => 'SER',
'Cinema' => 'CIN',
'Culture' => 'ARS',
'Culture pop' => 'CPO',
'Discovery' => 'DEC',
'History' => 'HIST',
'Science' => 'SCI',
'Other' => 'AUT'
]
],
],
'Collection' => [
'lang' => [
'type' => 'list',
'name' => 'Language',
'values' => [
'Français' => 'fr',
'Deutsch' => 'de',
'English' => 'en',
'Español' => 'es',
'Polski' => 'pl',
'Italiano' => 'it'
]
],
'col' => [
'name' => 'Collection id',
'required' => true,
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/',
'exampleValue' => 'RC-014095'
]
]
];
const PARAMETERS = array(
'Catégorie (Français)' => array(
'catfr' => array(
'type' => 'list',
'name' => 'Catégorie',
'values' => array(
'Toutes les vidéos (français)' => null,
'Actu & société' => 'ACT',
'Séries & fiction' => 'SER',
'Cinéma' => 'CIN',
'Arts & spectacles classiques' => 'ARS',
'Culture pop' => 'CPO',
'Découverte' => 'DEC',
'Histoire' => 'HIST',
'Science' => 'SCI',
'Autre' => '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/',
'exampleValue' => 'RC-014095'
)
),
'Catégorie (Allemand)' => array(
'catde' => array(
'type' => 'list',
'name' => 'Catégorie',
'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'
)
)
),
'Collection (Allemand)' => array(
'colde' => array(
'name' => 'Collection id',
'required' => true,
'title' => 'ex. RC-014095 pour https://www.arte.tv/de/videos/RC-014095/blow-up/',
'exampleValue' => 'RC-014095'
)
)
);
public function collectData()
{
switch ($this->queriedContext) {
case 'Category':
$category = $this->getInput('cat');
$collectionId = null;
break;
case 'Collection':
$collectionId = $this->getInput('col');
$category = null;
break;
}
public function collectData(){
switch($this->queriedContext) {
case 'Catégorie (Français)':
$category = $this->getInput('catfr');
$lang = 'fr';
break;
case 'Collection (Français)':
$lang = 'fr';
$collectionId = $this->getInput('colfr');
break;
case 'Catégorie (Allemand)':
$category = $this->getInput('catde');
$lang = 'de';
break;
case 'Collection (Allemand)':
$lang = 'de';
$collectionId = $this->getInput('colde');
break;
}
$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?sort=-lastModified&limit=10&language='
. $lang
. ($category != null ? '&category.code=' . $category : '')
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
$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 : '');
$header = array(
'Authorization: Bearer ' . self::API_TOKEN
);
$header = [
'Authorization: Bearer ' . self::API_TOKEN
];
$input = getContents($url, $header);
$input_json = json_decode($input, true);
$input = getContents($url, $header);
$input_json = json_decode($input, true);
foreach($input_json['videos'] as $element) {
foreach ($input_json['videos'] as $element) {
if ($this->getInput('exclude_trailers') && $element['platform'] == 'EXTRAIT') {
continue;
}
$item = array();
$item['uri'] = $element['url'];
$item['id'] = $element['id'];
$durationSeconds = $element['durationSeconds'];
$item['timestamp'] = strtotime($element['videoRightsBegin']);
$item['title'] = $element['title'];
$item = [];
$item['uri'] = $element['url'];
$item['id'] = $element['id'];
if(!empty($element['subtitle']))
$item['title'] = $element['title'] . ' | ' . $element['subtitle'];
$item['timestamp'] = strtotime($element['videoRightsBegin']);
$item['title'] = $element['title'];
$item['duration'] = round((int)$element['durationSeconds'] / 60);
$item['content'] = $element['teaserText']
. '<br><br>'
. $item['duration']
. 'min<br><a href="'
. $item['uri']
. '"><img src="'
. $element['mainImage']['url']
. '" /></a>';
if (!empty($element['subtitle'])) {
$item['title'] = $element['title'] . ' | ' . $element['subtitle'];
}
$durationMinutes = round((int)$durationSeconds / 60);
$item['content'] = $element['teaserText']
. '<br><br>'
. $durationMinutes
. 'min<br><a href="'
. $item['uri']
. '"><img src="'
. $element['mainImage']['url']
. '" /></a>';
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
}

View File

@@ -1,81 +1,71 @@
<?php
class AsahiShimbunAJWBridge extends BridgeAbstract {
const NAME = 'Asahi Shimbun AJW';
const BASE_URI = 'http://www.asahi.com';
const URI = self::BASE_URI . '/ajw/';
const DESCRIPTION = 'Asahi Shimbun - Asia & Japan Watch';
const MAINTAINER = 'somini';
const PARAMETERS = array(
array(
'section' => array(
'type' => 'list',
'name' => 'Section',
'values' => array(
'Japan » Social Affairs' => 'japan/social',
'Japan » People' => 'japan/people',
'Japan » 3/11 Disaster' => 'japan/0311disaster',
'Japan » Sci & Tech' => 'japan/sci_tech',
'Politics' => 'politics',
'Business' => 'business',
'Culture » Style' => 'culture/style',
'Culture » Movies' => 'culture/movies',
'Culture » Manga & Anime' => 'culture/manga_anime',
'Asia » China' => 'asia/china',
'Asia » Korean Peninsula' => 'asia/korean_peninsula',
'Asia » Around Asia' => 'asia/around_asia',
'Opinion » Editorial' => 'opinion/editorial',
'Opinion » Vox Populi' => 'opinion/vox',
),
'defaultValue' => 'politics',
)
)
);
class AsahiShimbunAJWBridge extends BridgeAbstract
{
const NAME = 'Asahi Shimbun AJW';
const BASE_URI = 'http://www.asahi.com';
const URI = self::BASE_URI . '/ajw/';
const DESCRIPTION = 'Asahi Shimbun - Asia & Japan Watch';
const MAINTAINER = 'somini';
const PARAMETERS = [
[
'section' => [
'type' => 'list',
'name' => 'Section',
'values' => [
'Japan » Social Affairs' => 'japan/social',
'Japan » People' => 'japan/people',
'Japan » 3/11 Disaster' => 'japan/0311disaster',
'Japan » Sci & Tech' => 'japan/sci_tech',
'Politics' => 'politics',
'Business' => 'business',
'Culture » Style' => 'culture/style',
'Culture » Movies' => 'culture/movies',
'Culture » Manga & Anime' => 'culture/manga_anime',
'Asia » China' => 'asia_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',
]
]
];
private function getSectionURI($section) {
return self::getURI() . $section . '/';
}
private function getSectionURI($section)
{
return $this->getURI() . $section . '/';
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')));
public function collectData()
{
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')));
foreach($html->find('#MainInner li a') as $element) {
if ($element->parent()->class == 'HeadlineTopImage-S') {
Debug::log('Skip Headline, it is repeated below');
continue;
}
$item = array();
foreach ($html->find('#MainInner li a') as $element) {
if ($element->parent()->class == 'HeadlineTopImage-S') {
Debug::log('Skip Headline, it is repeated below');
continue;
}
$item = [];
$item['uri'] = self::BASE_URI . $element->href;
$e_lead = $element->find('span.Lead', 0);
if ($e_lead) {
$item['content'] = $e_lead->innertext;
$e_lead->outertext = '';
} else {
$item['content'] = $element->innertext;
}
$e_date = $element->find('span.EnDate', 0);
if ($e_date) {
$item['timestamp'] = strtotime($e_date->innertext);
$e_date->outertext = '';
}
$e_video = $element->find('span.EnVideo', 0);
if ($e_video) {
$e_video->outertext = '';
$element->innertext = "VIDEO: $element->innertext";
}
$item['title'] = $element->innertext;
$item['uri'] = self::BASE_URI . $element->href;
$e_lead = $element->find('span.Lead', 0);
if ($e_lead) {
$item['content'] = $e_lead->innertext;
$e_lead->outertext = '';
} else {
$item['content'] = $element->innertext;
}
$e_date = $element->find('span.EnDate', 0);
if ($e_date) {
$item['timestamp'] = strtotime($e_date->innertext);
$e_date->outertext = '';
}
$e_video = $element->find('span.EnVideo', 0);
if ($e_video) {
$e_video->outertext = '';
$element->innertext = "VIDEO: $element->innertext";
}
$e_title = $element->find('.title', 0);
if ($e_title) {
$item['title'] = $e_title->innertext;
} else {
$item['title'] = $element->innertext;
}
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
}

View File

@@ -1,79 +1,74 @@
<?php
class AskfmBridge extends BridgeAbstract {
class AskfmBridge extends BridgeAbstract
{
const MAINTAINER = 'az5he6ch, logmanoriginal';
const NAME = 'Ask.fm Answers';
const URI = 'https://ask.fm/';
const CACHE_TIMEOUT = 300; //5 min
const DESCRIPTION = 'Returns answers from an Ask.fm user';
const PARAMETERS = [
'Ask.fm username' => [
'u' => [
'name' => 'Username',
'required' => true,
'exampleValue' => 'ApprovedAndReal'
]
]
];
const MAINTAINER = 'az5he6ch, logmanoriginal';
const NAME = 'Ask.fm Answers';
const URI = 'https://ask.fm/';
const CACHE_TIMEOUT = 300; //5 min
const DESCRIPTION = 'Returns answers from an Ask.fm user';
const PARAMETERS = array(
'Ask.fm username' => array(
'u' => array(
'name' => 'Username',
'required' => true,
'exampleValue' => 'ApprovedAndReal'
)
)
);
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI());
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI());
$html = defaultLinkTo($html, self::URI);
$html = defaultLinkTo($html, self::URI);
foreach ($html->find('article.streamItem-answer') as $element) {
$item = [];
$item['uri'] = $element->find('a.streamItem_meta', 0)->href;
$question = trim($element->find('header.streamItem_header', 0)->innertext);
foreach($html->find('article.streamItem-answer') as $element) {
$item = array();
$item['uri'] = $element->find('a.streamItem_meta', 0)->href;
$question = trim($element->find('header.streamItem_header', 0)->innertext);
$item['title'] = trim(
htmlspecialchars_decode(
$element->find('header.streamItem_header', 0)->plaintext,
ENT_QUOTES
)
);
$item['title'] = trim(
htmlspecialchars_decode($element->find('header.streamItem_header', 0)->plaintext,
ENT_QUOTES
)
);
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
$answer = trim($element->find('div.streamItem_content', 0)->innertext);
$answer = trim($element->find('div.streamItem_content', 0)->innertext);
// This probably should be cleaned up, especially for YouTube embeds
if ($visual = $element->find('div.streamItem_visual', 0)) {
$visual = $visual->innertext;
}
// This probably should be cleaned up, especially for YouTube embeds
if($visual = $element->find('div.streamItem_visual', 0)) {
$visual = $visual->innertext;
}
// Fix tracking links, also doesn't work
foreach ($element->find('a') as $link) {
if (strpos($link->href, 'l.ask.fm') !== false) {
$link->href = $link->plaintext;
}
}
// Fix tracking links, also doesn't work
foreach($element->find('a') as $link) {
if(strpos($link->href, 'l.ask.fm') !== false) {
$link->href = $link->plaintext;
}
}
$item['content'] = '<p>' . $question
. '</p><p>' . $answer
. '</p><p>' . $visual . '</p>';
$item['content'] = '<p>' . $question
. '</p><p>' . $answer
. '</p><p>' . $visual . '</p>';
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
public function getName()
{
if (!is_null($this->getInput('u'))) {
return self::NAME . ' : ' . $this->getInput('u');
}
public function getName(){
if(!is_null($this->getInput('u'))) {
return self::NAME . ' : ' . $this->getInput('u');
}
return parent::getName();
}
return parent::getName();
}
public function getURI()
{
if (!is_null($this->getInput('u'))) {
return self::URI . urlencode($this->getInput('u'));
}
public function getURI(){
if(!is_null($this->getInput('u'))) {
return self::URI . urlencode($this->getInput('u'));
}
return parent::getURI();
}
return parent::getURI();
}
}

View File

@@ -1,281 +0,0 @@
<?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 = [
'Standard Topics' => [
'topic' => [
'name' => 'Topic',
'type' => 'list',
'values' => [
'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' => [
'topic' => [
'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 = [];
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 = [];
// 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'])) {
$leadPhotoUrl = sprintf('https://storage.googleapis.com/afs-prod/media/%s/800.jpeg', $storyContent['leadPhotoId']);
$leadPhotoImageTag = sprintf('<img src="%s">', $leadPhotoUrl);
// Move the image to the beginning of the content
$html = $leadPhotoImageTag . $html;
// Explicitly not adding it to the item's enclosures!
}
}
$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 = '';
}
}
}
}
}

View File

@@ -1,53 +0,0 @@
<?php
class AstrophysicsDataSystemBridge extends BridgeAbstract
{
const NAME = 'SAO/NASA Astrophysics Data System';
const DESCRIPTION = 'Returns the latest publications from a query';
const URI = 'https://ui.adsabs.harvard.edu';
const PARAMETERS = [
'Publications' => [
'query' => [
'name' => 'query',
'title' => 'Same format as the search bar on the website',
'exampleValue' => 'author:"huchra, john"',
'required' => true
]
]];
private $feedTitle;
public function getName()
{
if ($this->queriedContext === 'Publications') {
return $this->feedTitle;
}
return parent::getName();
}
public function getURI()
{
if ($this->queriedContext === 'Publications') {
return self::URI . '/search/?q=' . urlencode($this->getInput('query'));
}
return parent::getURI();
}
public function collectData()
{
$headers = [
'Cookie: core=always;'
];
$html = str_get_html(defaultLinkTo(getContents($this->getURI(), $headers), self::URI));
$this->feedTitle = html_entity_decode($html->find('title', 0)->plaintext);
foreach ($html->find('div.row > ul > li') as $pub) {
$item = [];
$item['title'] = $pub->find('h3.s-results-title', 0)->plaintext;
$item['content'] = $pub->find('div.s-results-links', 0);
$item['uri'] = $pub->find('a.abs-redirect-link', 0)->href;
$item['author'] = rtrim($pub->find('li.article-author', 0)->plaintext, ' ;');
$item['timestamp'] = $pub->find('div[aria-label="date published"]', 0)->plaintext;
$this->items[] = $item;
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,60 +1,58 @@
<?php
class AtmoOccitanieBridge extends BridgeAbstract {
class AtmoOccitanieBridge extends BridgeAbstract
{
const NAME = 'Atmo Occitanie';
const URI = 'https://www.atmo-occitanie.org/';
const DESCRIPTION = 'Fetches the latest air polution of cities in Occitanie from Atmo';
const MAINTAINER = 'floviolleau';
const PARAMETERS = [[
'city' => [
'name' => 'Ville',
'required' => true,
'exampleValue' => 'cahors'
]
]];
const CACHE_TIMEOUT = 7200;
const NAME = 'Atmo Occitanie';
const URI = 'https://www.atmo-occitanie.org/';
const DESCRIPTION = 'Fetches the latest air polution of cities in Occitanie from Atmo';
const MAINTAINER = 'floviolleau';
const PARAMETERS = array(array(
'city' => array(
'name' => 'Ville',
'required' => true,
'exampleValue' => 'cahors'
)
));
const CACHE_TIMEOUT = 7200;
public function collectData()
{
$uri = self::URI . $this->getInput('city');
public function collectData() {
$uri = self::URI . $this->getInput('city');
$html = getSimpleHTMLDOM($uri);
$html = getSimpleHTMLDOM($uri);
$generalMessage = $html->find('.landing-ville .city-banner .iqa-avertissement', 0)->innertext;
$recommendationsDom = $html->find('.landing-ville .recommandations', 0);
$recommendationsItemDom = $recommendationsDom->find('.recommandation-item .label');
$generalMessage = $html->find('.landing-ville .city-banner .iqa-avertissement', 0)->innertext;
$recommendationsDom = $html->find('.landing-ville .recommandations', 0);
$recommendationsItemDom = $recommendationsDom->find('.recommandation-item .label');
$recommendationsMessage = '';
$recommendationsMessage = '';
$i = 0;
$len = count($recommendationsItemDom);
foreach ($recommendationsItemDom as $key => $value) {
if ($i == 0) {
$recommendationsMessage .= trim($value->innertext) . '.';
} else {
$recommendationsMessage .= ' ' . trim($value->innertext) . '.';
}
$i++;
}
$i = 0;
$len = count($recommendationsItemDom);
foreach ($recommendationsItemDom as $key => $value) {
if ($i == 0) {
$recommendationsMessage .= trim($value->innertext) . '.';
} else {
$recommendationsMessage .= ' ' . trim($value->innertext) . '.';
}
$i++;
}
$lastRecommendationsDom = $recommendationsDom->find('.col-md-6', -1);
$informationHeaderMessage = $lastRecommendationsDom->find('.heading', 0)->innertext;
$indice = $lastRecommendationsDom->find('.current-indice .indice div', 0)->innertext;
$informationDescriptionMessage = $lastRecommendationsDom->find('.current-indice .description p', 0)->innertext;
$lastRecommendationsDom = $recommendationsDom->find('.col-md-6', -1);
$informationHeaderMessage = $lastRecommendationsDom->find('.heading', 0)->innertext;
$indice = $lastRecommendationsDom->find('.current-indice .indice div', 0)->innertext;
$informationDescriptionMessage = $lastRecommendationsDom->find('.current-indice .description p', 0)->innertext;
$message = "$generalMessage L'indice est de $indice/10. $informationDescriptionMessage. $recommendationsMessage";
$city = $this->getInput('city');
$message = "$generalMessage L'indice est de $indice/10. $informationDescriptionMessage. $recommendationsMessage";
$city = $this->getInput('city');
$item['uri'] = $uri;
$today = date('d/m/Y');
$item['title'] = "Bulletin de l'air du $today pour la ville : $city.";
//$item['title'] .= ' Retrouvez plus d\'informations en allant sur atmo-occitanie.org #QualiteAir. ' . $message;
$item['title'] .= ' #QualiteAir. ' . $message;
$item['author'] = 'floviolleau';
$item['content'] = $message;
$item['uid'] = hash('sha256', $item['title']);
$item['uri'] = $uri;
$today = date('d/m/Y');
$item['title'] = "Bulletin de l'air du $today pour la ville : $city.";
//$item['title'] .= ' Retrouvez plus d\'informations en allant sur atmo-occitanie.org #QualiteAir. ' . $message;
$item['title'] .= ' #QualiteAir. ' . $message;
$item['author'] = 'floviolleau';
$item['content'] = $message;
$item['uid'] = hash('sha256', $item['title']);
$this->items[] = $item;
}
$this->items[] = $item;
}
}

View File

@@ -1,135 +1,135 @@
<?php
class AutoJMBridge extends BridgeAbstract
{
const NAME = 'AutoJM';
const URI = 'https://www.autojm.fr/';
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = [
'Afficher les offres de véhicules disponible sur la recheche AutoJM' => [
'url' => [
'name' => 'URL de la page de recherche',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
'exampleValue' => 'recherche?brands[]=peugeot&ranges[]=peugeot-nouvelle-308-2021-5p'
],
]
];
const CACHE_TIMEOUT = 3600;
class AutoJMBridge extends BridgeAbstract {
public function getIcon()
{
return self::URI . 'favicon.ico';
}
const NAME = 'AutoJM';
const URI = 'https://www.autojm.fr/';
const DESCRIPTION = 'Suivre les offres de véhicules proposés par AutoJM en fonction des critères de filtrages';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
'Afficher les offres de véhicules disponible sur la recheche AutoJM' => array(
'url' => array(
'name' => 'URL de la page de recherche',
'type' => 'text',
'required' => true,
'title' => 'URL d\'une recherche avec filtre de véhicules sans le http://www.autojm.fr/',
'exampleValue' => 'recherche?brands[]=peugeot&ranges[]=peugeot-nouvelle-308-2021-5p'
),
)
);
const CACHE_TIMEOUT = 3600;
public function getName()
{
switch ($this->queriedContext) {
case 'Afficher les offres de véhicules disponible sur la recheche AutoJM':
return 'AutoJM | Recherche de véhicules';
break;
default:
return parent::getName();
}
}
public function getIcon() {
return self::URI . 'favicon.ico';
}
public function collectData()
{
// Get the number of result for this search
$search_url = self::URI . $this->getInput('url') . '&open=energy&onlyFilters=false';
public function getName() {
switch($this->queriedContext) {
case 'Afficher les offres de véhicules disponible sur la recheche AutoJM':
return 'AutoJM | Recherche de véhicules';
break;
default:
return parent::getName();
}
// Set the header 'X-Requested-With' like the website does it
$header = [
'X-Requested-With: XMLHttpRequest'
];
}
// Get the JSON content of the form
$json = getContents($search_url, $header);
public function collectData() {
// Extract the HTML content from the JSON result
$data = json_decode($json);
// Get the number of result for this search
$search_url = self::URI . $this->getInput('url') . '&open=energy&onlyFilters=false';
$nb_results = $data->nbResults;
$total_pages = ceil($nb_results / 15);
// Set the header 'X-Requested-With' like the website does it
$header = array(
'X-Requested-With: XMLHttpRequest'
);
// Limit the number of page to analyse to 10
for ($page = 1; $page <= $total_pages && $page <= 10; $page++) {
// Get the result the next page
$html = $this->getResults($page);
// Get the JSON content of the form
$json = getContents($search_url, $header);
// Go through every car of the search
$list = $html->find('div[class*=card-car card-car--listing]');
foreach ($list as $car) {
// Get the info about the car offer
$image = $car->find('div[class=card-car__header__img]', 0)->find('img', 0)->src;
// Decode HTML attribute JSON data
$car_data = json_decode(html_entity_decode($car->{'data-layer'}));
$car_model = $car->{'data-title'} . ' ' . $car->{'data-suptitle'};
$availability = $car->find('div[class=card-car__modalites]', 0)->find('div[class=col]', 0)->plaintext;
$warranty = $car->find('div[data-type=WarrantyCard]', 0)->plaintext;
$discount_html = $car->find('div[class=subtext vehicle_reference_element]', 0);
// Check if there is any discount info displayed
if ($discount_html != null) {
$reference_price_value = $discount_html->find('span[data-cfg=vehicle__reference_price]', 0)->plaintext;
$discount_percent_value = $discount_html->find('span[data-cfg=vehicle__discount_percent]', 0)->plaintext;
$reference_price = '<li>Prix de référence : <s>' . $reference_price_value . '</s></li>';
$discount_percent = '<li>Réduction : ' . $discount_percent_value . ' %</li>';
} else {
$reference_price = '';
$discount_percent = '';
}
$price = $car_data->price;
$kilometer = $car->find('span[data-cfg=vehicle__kilometer]', 0)->plaintext;
$energy = $car->find('span[data-cfg=vehicle__energy__label]', 0)->plaintext;
$power = $car->find('span[data-cfg=vehicle__tax_horse_power]', 0)->plaintext;
$seats = $car->find('span[data-cfg=vehicle__seats]', 0)->plaintext;
$doors = $car->find('span[data-cfg=vehicle__door__label]', 0)->plaintext;
$transmission = $car->find('span[data-cfg=vehicle__transmission]', 0)->plaintext;
$loa_html = $car->find('span[data-cfg=vehicle__loa]', 0);
// Check if any LOA price is displayed
if ($loa_html != null) {
$loa_value = $car->find('span[data-cfg=vehicle__loa]', 0)->plaintext;
$loa = '<li>LOA : à partir de ' . $loa_value . ' / mois </li>';
} else {
$loa = '';
}
// Extract the HTML content from the JSON result
$data = json_decode($json);
// Construct the new item
$item = [];
$item['title'] = $car_model;
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
. $car_model . '</p>';
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
$item['content'] .= '<li>Prix : ' . $price . ' €</li>';
$item['content'] .= $reference_price;
$item['content'] .= $loa;
$item['content'] .= $discount_percent;
$item['content'] .= '<li>Garantie : ' . $warranty . '</li>';
$item['content'] .= '<li>Kilométrage : ' . $kilometer . ' km</li>';
$item['content'] .= '<li>Energie : ' . $energy . '</li>';
$item['content'] .= '<li>Puissance: ' . $power . ' CV Fiscaux</li>';
$item['content'] .= '<li>Nombre de Places : ' . $seats . ' place(s)</li>';
$item['content'] .= '<li>Nombre de portes : ' . $doors . '</li>';
$item['content'] .= '<li>Boite de vitesse : ' . $transmission . '</li></ul>';
$item['uri'] = $car_data->{'uri'};
$item['uid'] = hash('md5', $item['content']);
$this->items[] = $item;
}
}
}
$nb_results = $data->nbResults;
$total_pages = ceil($nb_results / 15);
private function getResults(int $page)
{
$user_input = $this->getInput('url');
$search_data = preg_replace('#(recherche|recherche/[0-9]{1,10})\?#', 'recherche/' . $page . '?', $user_input);
// Limit the number of page to analyse to 10
for($page = 1; $page <= $total_pages && $page <= 10; $page++) {
// Get the result the next page
$html = $this->getResults($page);
$search_url = self::URI . $search_data . '&open=energy&onlyFilters=false';
// Go through every car of the search
$list = $html->find('div[class*=card-car card-car--listing]');
foreach ($list as $car) {
// Get the HTML content of the page
$html = getSimpleHTMLDOMCached($search_url);
// Get the info about the car offer
$image = $car->find('div[class=card-car__header__img]', 0)->find('img', 0)->src;
// Decode HTML attribute JSON data
$car_data = json_decode(html_entity_decode($car->{'data-layer'}));
$car_model = $car->{'data-title'} . ' ' . $car->{'data-suptitle'};
$availability = $car->find('div[class=card-car__modalites]', 0)->find('div[class=col]', 0)->plaintext;
$warranty = $car->find('div[data-type=WarrantyCard]', 0)->plaintext;
$discount_html = $car->find('div[class=subtext vehicle_reference_element]', 0);
// Check if there is any discount info displayed
if ($discount_html != null) {
$reference_price_value = $discount_html->find('span[data-cfg=vehicle__reference_price]', 0)->plaintext;
$discount_percent_value = $discount_html->find('span[data-cfg=vehicle__discount_percent]', 0)->plaintext;
$reference_price = '<li>Prix de référence : <s>' . $reference_price_value . '</s></li>';
$discount_percent = '<li>Réduction : ' . $discount_percent_value . ' %</li>';
} else {
$reference_price = '';
$discount_percent = '';
}
$price = $car_data->price;
$kilometer = $car->find('span[data-cfg=vehicle__kilometer]', 0)->plaintext;
$energy = $car->find('span[data-cfg=vehicle__energy__label]', 0)->plaintext;
$power = $car->find('span[data-cfg=vehicle__tax_horse_power]', 0)->plaintext;
$seats = $car->find('span[data-cfg=vehicle__seats]', 0)->plaintext;
$doors = $car->find('span[data-cfg=vehicle__door__label]', 0)->plaintext;
$transmission = $car->find('span[data-cfg=vehicle__transmission]', 0)->plaintext;
$loa_html = $car->find('span[data-cfg=vehicle__loa]', 0);
// Check if any LOA price is displayed
if($loa_html != null) {
$loa_value = $car->find('span[data-cfg=vehicle__loa]', 0)->plaintext;
$loa = '<li>LOA : à partir de ' . $loa_value . ' / mois </li>';
} else {
$loa = '';
}
return $html;
}
// Construct the new item
$item = array();
$item['title'] = $car_model;
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
. $car_model . '</p>';
$item['content'] .= '<ul><li>Disponibilité : ' . $availability . '</li>';
$item['content'] .= '<li>Prix : ' . $price . ' €</li>';
$item['content'] .= $reference_price;
$item['content'] .= $loa;
$item['content'] .= $discount_percent;
$item['content'] .= '<li>Garantie : ' . $warranty . '</li>';
$item['content'] .= '<li>Kilométrage : ' . $kilometer . ' km</li>';
$item['content'] .= '<li>Energie : ' . $energy . '</li>';
$item['content'] .= '<li>Puissance: ' . $power . ' CV Fiscaux</li>';
$item['content'] .= '<li>Nombre de Places : ' . $seats . ' place(s)</li>';
$item['content'] .= '<li>Nombre de portes : ' . $doors . '</li>';
$item['content'] .= '<li>Boite de vitesse : ' . $transmission . '</li></ul>';
$item['uri'] = $car_data->{'uri'};
$item['uid'] = hash('md5', $item['content']);
$this->items[] = $item;
}
}
}
private function getResults(int $page)
{
$user_input = $this->getInput('url');
$search_data = preg_replace('#(recherche|recherche/[0-9]{1,10})\?#', 'recherche/' . $page . '?', $user_input);
$search_url = self::URI . $search_data . '&open=energy&onlyFilters=false';
// Get the HTML content of the page
$html = getSimpleHTMLDOMCached($search_url);
return $html;
}
}

View File

@@ -1,59 +1,54 @@
<?php
class AwwwardsBridge extends BridgeAbstract {
const NAME = 'Awwwards';
const URI = 'https://www.awwwards.com/';
const DESCRIPTION = 'Fetches the latest ten sites of the day from Awwwards';
const MAINTAINER = 'Paroleen';
const CACHE_TIMEOUT = 3600;
class AwwwardsBridge extends BridgeAbstract
{
const NAME = 'Awwwards';
const URI = 'https://www.awwwards.com/';
const DESCRIPTION = 'Fetches the latest ten sites of the day from Awwwards';
const MAINTAINER = 'Paroleen';
const CACHE_TIMEOUT = 3600;
const SITESURI = 'https://www.awwwards.com/websites/sites_of_the_day/';
const SITEURI = 'https://www.awwwards.com/sites/';
const ASSETSURI = 'https://assets.awwwards.com/awards/media/cache/thumb_417_299/';
const SITESURI = 'https://www.awwwards.com/websites/sites_of_the_day/';
const SITEURI = 'https://www.awwwards.com/sites/';
const ASSETSURI = 'https://assets.awwwards.com/awards/media/cache/thumb_417_299/';
private $sites = array();
private $sites = [];
public function getIcon() {
return 'https://www.awwwards.com/favicon.ico';
}
public function getIcon()
{
return 'https://www.awwwards.com/favicon.ico';
}
private function fetchSites() {
Debug::log('Fetching all sites');
$sites = getSimpleHTMLDOM(self::SITESURI);
private function fetchSites()
{
Debug::log('Fetching all sites');
$sites = getSimpleHTMLDOM(self::SITESURI);
Debug::log('Parsing all JSON data');
foreach($sites->find('li[data-model]') as $site) {
$decode = html_entity_decode($site->attr['data-model'],
ENT_QUOTES, 'utf-8');
$decode = json_decode($decode, true);
$this->sites[] = $decode;
}
}
Debug::log('Parsing all JSON data');
foreach ($sites->find('.grid-sites li') as $site) {
$decode = html_entity_decode($site->attr['data-collectable-model-value'], ENT_QUOTES, 'utf-8');
$decode = json_decode($decode, true);
$this->sites[] = $decode;
}
}
public function collectData() {
$this->fetchSites();
public function collectData()
{
$this->fetchSites();
Debug::log('Building RSS feed');
foreach($this->sites as $site) {
$item = array();
$item['title'] = $site['title'];
$item['timestamp'] = $site['createdAt'];
$item['categories'] = $site['tags'];
Debug::log('Building RSS feed');
foreach ($this->sites as $site) {
$item = [];
$item['title'] = $site['title'];
$item['timestamp'] = $site['createdAt'];
$item['categories'] = $site['tags'];
$item['content'] = '<img src="'
. self::ASSETSURI
. $site['images']['thumbnail']
. '">';
$item['uri'] = self::SITEURI . $site['slug'];
$item['content'] = '<img src="'
. self::ASSETSURI
. $site['images']['thumbnail']
. '">';
$item['uri'] = self::SITEURI . $site['slug'];
$this->items[] = $item;
$this->items[] = $item;
if (count($this->items) >= 10) {
break;
}
}
}
if(count($this->items) >= 10)
break;
}
}
}

View File

@@ -1,269 +1,263 @@
<?php
class BAEBridge extends BridgeAbstract {
const MAINTAINER = 'couraudt';
const NAME = 'Bourse Aux Equipiers Bridge';
const URI = 'https://www.bourse-aux-equipiers.com';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = array(
array(
'keyword' => array(
'name' => 'Filtrer par mots clés',
'title' => 'Entrez le mot clé à filtrer ici'
),
'type' => array(
'name' => 'Type de recherche',
'title' => 'Afficher seuleument un certain type d\'annonce',
'type' => 'list',
'values' => array(
'Toutes les annonces' => false,
'Les embarquements' => 'boat',
'Les skippers' => 'skipper',
'Les équipiers' => 'crew'
)
)
)
);
class BAEBridge extends BridgeAbstract
{
const MAINTAINER = 'couraudt';
const NAME = 'Bourse Aux Equipiers Bridge';
const URI = 'https://www.bourse-aux-equipiers.com';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = [
[
'keyword' => [
'name' => 'Filtrer par mots clés',
'title' => 'Entrez le mot clé à filtrer ici'
],
'type' => [
'name' => 'Type de recherche',
'title' => 'Afficher seuleument un certain type d\'annonce',
'type' => 'list',
'values' => [
'Toutes les annonces' => false,
'Les embarquements' => 'boat',
'Les skippers' => 'skipper',
'Les équipiers' => 'crew'
]
]
]
];
public function collectData() {
$url = $this->getURI();
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
public function collectData()
{
$url = $this->getURI();
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
$annonces = $html->find('main article');
foreach ($annonces as $annonce) {
$detail = $annonce->find('footer a', 0);
$annonces = $html->find('main article');
foreach ($annonces as $annonce) {
$detail = $annonce->find('footer a', 0);
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
if (!$htmlDetail)
continue;
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
if (!$htmlDetail) {
continue;
}
$item = array();
$item = [];
$item['title'] = $annonce->find('header h2', 0)->plaintext;
$item['uri'] = parent::getURI() . $detail->href;
$item['title'] = $annonce->find('header h2', 0)->plaintext;
$item['uri'] = parent::getURI() . $detail->href;
$content = $htmlDetail->find('article p', 0)->innertext;
if (!empty($this->getInput('keyword'))) {
$keyword = $this->remove_accents(strtolower($this->getInput('keyword')));
$cleanTitle = $this->remove_accents(strtolower($item['title']));
if (strpos($cleanTitle, $keyword) === false) {
$cleanContent = $this->remove_accents(strtolower($content));
if (strpos($cleanContent, $keyword) === false) {
continue;
}
}
}
$content = $htmlDetail->find('article p', 0)->innertext;
if (!empty($this->getInput('keyword'))) {
$keyword = $this->removeAccents(strtolower($this->getInput('keyword')));
$cleanTitle = $this->removeAccents(strtolower($item['title']));
if (strpos($cleanTitle, $keyword) === false) {
$cleanContent = $this->removeAccents(strtolower($content));
if (strpos($cleanContent, $keyword) === false) {
continue;
}
}
}
$content .= '<hr>';
$content .= $htmlDetail->find('section', 0)->innertext;
$item['content'] = defaultLinkTo($content, parent::getURI());
$image = $htmlDetail->find('#zoom', 0);
if ($image) {
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
}
$this->items[] = $item;
}
}
$content .= '<hr>';
$content .= $htmlDetail->find('section', 0)->innertext;
$item['content'] = defaultLinkTo($content, parent::getURI());
$image = $htmlDetail->find('#zoom', 0);
if ($image) {
$item['enclosures'] = [parent::getURI() . $image->getAttribute('src')];
}
$this->items[] = $item;
}
}
public function getURI() {
$uri = parent::getURI();
if (!empty($this->getInput('type'))) {
if ($this->getInput('type') == 'boat') {
$uri .= '/embarquements.html';
} elseif ($this->getInput('type') == 'skipper') {
$uri .= '/skippers.html';
} else {
$uri .= '/equipiers.html';
}
}
public function getURI()
{
$uri = parent::getURI();
if (!empty($this->getInput('type'))) {
if ($this->getInput('type') == 'boat') {
$uri .= '/embarquements.html';
} elseif ($this->getInput('type') == 'skipper') {
$uri .= '/skippers.html';
} else {
$uri .= '/equipiers.html';
}
}
return $uri;
}
return $uri;
}
private function remove_accents($string) {
$chars = array(
// Decompositions for Latin-1 Supplement
'ª' => 'a', 'º' => 'o',
'À' => 'A', 'Á' => 'A',
'Â' => 'A', 'Ã' => 'A',
'Ä' => 'A', 'Å' => 'A',
'Æ' => 'AE', 'Ç' => 'C',
'È' => 'E', 'É' => 'E',
'Ê' => 'E', 'Ë' => 'E',
'Ì' => 'I', 'Í' => 'I',
'Î' => 'I', 'Ï' => 'I',
'Ð' => 'D', 'Ñ' => 'N',
'Ò' => 'O', 'Ó' => 'O',
'Ô' => 'O', 'Õ' => 'O',
'Ö' => 'O', 'Ù' => 'U',
'Ú' => 'U', 'Û' => 'U',
'Ü' => 'U', 'Ý' => 'Y',
'Þ' => 'TH', 'ß' => 's',
'à' => 'a', 'á' => 'a',
'â' => 'a', 'ã' => 'a',
'ä' => 'a', 'å' => 'a',
'æ' => 'ae', 'ç' => 'c',
'è' => 'e', 'é' => 'e',
'ê' => 'e', 'ë' => 'e',
'ì' => 'i', 'í' => 'i',
'î' => 'i', 'ï' => 'i',
'ð' => 'd', 'ñ' => 'n',
'ò' => 'o', 'ó' => 'o',
'ô' => 'o', 'õ' => 'o',
'ö' => 'o', 'ø' => 'o',
'ù' => 'u', 'ú' => 'u',
'û' => 'u', 'ü' => 'u',
'ý' => 'y', 'þ' => 'th',
'ÿ' => 'y', 'Ø' => 'O',
// Decompositions for Latin Extended-A
'Ā' => 'A', 'ā' => 'a',
'Ă' => 'A', 'ă' => 'a',
'Ą' => 'A', 'ą' => 'a',
'Ć' => 'C', 'ć' => 'c',
'Ĉ' => 'C', 'ĉ' => 'c',
'Ċ' => 'C', 'ċ' => 'c',
'Č' => 'C', 'č' => 'c',
'Ď' => 'D', 'ď' => 'd',
'Đ' => 'D', 'đ' => 'd',
'Ē' => 'E', 'ē' => 'e',
'Ĕ' => 'E', 'ĕ' => 'e',
'Ė' => 'E', 'ė' => 'e',
'Ę' => 'E', 'ę' => 'e',
'Ě' => 'E', 'ě' => 'e',
'Ĝ' => 'G', 'ĝ' => 'g',
'Ğ' => 'G', 'ğ' => 'g',
'Ġ' => 'G', 'ġ' => 'g',
'Ģ' => 'G', 'ģ' => 'g',
'Ĥ' => 'H', 'ĥ' => 'h',
'Ħ' => 'H', 'ħ' => 'h',
'Ĩ' => 'I', 'ĩ' => 'i',
'Ī' => 'I', 'ī' => 'i',
'Ĭ' => 'I', 'ĭ' => 'i',
'Į' => 'I', 'į' => 'i',
'İ' => 'I', 'ı' => 'i',
'IJ' => 'IJ', 'ij' => 'ij',
'Ĵ' => 'J', 'ĵ' => 'j',
'Ķ' => 'K', 'ķ' => 'k',
'ĸ' => 'k', 'Ĺ' => 'L',
'ĺ' => 'l', 'Ļ' => 'L',
'ļ' => 'l', 'Ľ' => 'L',
'ľ' => 'l', 'Ŀ' => 'L',
'ŀ' => 'l', 'Ł' => 'L',
'ł' => 'l', 'Ń' => 'N',
'ń' => 'n', 'Ņ' => 'N',
'ņ' => 'n', 'Ň' => 'N',
'ň' => 'n', 'ʼn' => 'n',
'Ŋ' => 'N', 'ŋ' => 'n',
'Ō' => 'O', 'ō' => 'o',
'Ŏ' => 'O', 'ŏ' => 'o',
'Ő' => 'O', 'ő' => 'o',
'Œ' => 'OE', 'œ' => 'oe',
'Ŕ' => 'R', 'ŕ' => 'r',
'Ŗ' => 'R', 'ŗ' => 'r',
'Ř' => 'R', 'ř' => 'r',
'Ś' => 'S', 'ś' => 's',
'Ŝ' => 'S', 'ŝ' => 's',
'Ş' => 'S', 'ş' => 's',
'Š' => 'S', 'š' => 's',
'Ţ' => 'T', 'ţ' => 't',
'Ť' => 'T', 'ť' => 't',
'Ŧ' => 'T', 'ŧ' => 't',
'Ũ' => 'U', 'ũ' => 'u',
'Ū' => 'U', 'ū' => 'u',
'Ŭ' => 'U', 'ŭ' => 'u',
'Ů' => 'U', 'ů' => 'u',
'Ű' => 'U', 'ű' => 'u',
'Ų' => 'U', 'ų' => 'u',
'Ŵ' => 'W', 'ŵ' => 'w',
'Ŷ' => 'Y', 'ŷ' => 'y',
'Ÿ' => 'Y', 'Ź' => 'Z',
'ź' => 'z', 'Ż' => 'Z',
'ż' => 'z', 'Ž' => 'Z',
'ž' => 'z', 'ſ' => 's',
// Decompositions for Latin Extended-B
'Ș' => 'S', 'ș' => 's',
'Ț' => 'T', 'ț' => 't',
// Euro Sign
'€' => 'E',
// GBP (Pound) Sign
'£' => '',
// Vowels with diacritic (Vietnamese)
// unmarked
'Ơ' => 'O', 'ơ' => 'o',
'Ư' => 'U', 'ư' => 'u',
// grave accent
'Ầ' => 'A', 'ầ' => 'a',
'Ằ' => 'A', 'ằ' => 'a',
'Ề' => 'E', 'ề' => 'e',
'Ồ' => 'O', 'ồ' => 'o',
'Ờ' => 'O', 'ờ' => 'o',
'Ừ' => 'U', 'ừ' => 'u',
'Ỳ' => 'Y', 'ỳ' => 'y',
// hook
'Ả' => 'A', 'ả' => 'a',
'Ẩ' => 'A', 'ẩ' => 'a',
'Ẳ' => 'A', 'ẳ' => 'a',
'Ẻ' => 'E', 'ẻ' => 'e',
'Ể' => 'E', 'ể' => 'e',
'Ỉ' => 'I', 'ỉ' => 'i',
'Ỏ' => 'O', 'ỏ' => 'o',
'Ổ' => 'O', 'ổ' => 'o',
'Ở' => 'O', 'ở' => 'o',
'Ủ' => 'U', 'ủ' => 'u',
'Ử' => 'U', 'ử' => 'u',
'Ỷ' => 'Y', 'ỷ' => 'y',
// tilde
'Ẫ' => 'A', 'ẫ' => 'a',
'Ẵ' => 'A', 'ẵ' => 'a',
'Ẽ' => 'E', 'ẽ' => 'e',
'Ễ' => 'E', 'ễ' => 'e',
'Ỗ' => 'O', 'ỗ' => 'o',
'Ỡ' => 'O', 'ỡ' => 'o',
'Ữ' => 'U', 'ữ' => 'u',
'Ỹ' => 'Y', 'ỹ' => 'y',
// acute accent
'Ấ' => 'A', 'ấ' => 'a',
'Ắ' => 'A', 'ắ' => 'a',
'Ế' => 'E', 'ế' => 'e',
'Ố' => 'O', 'ố' => 'o',
'Ớ' => 'O', 'ớ' => 'o',
'Ứ' => 'U', 'ứ' => 'u',
// dot below
'Ạ' => 'A', 'ạ' => 'a',
'Ậ' => 'A', 'ậ' => 'a',
'Ặ' => 'A', 'ặ' => 'a',
'Ẹ' => 'E', 'ẹ' => 'e',
'Ệ' => 'E', 'ệ' => 'e',
'Ị' => 'I', 'ị' => 'i',
'Ọ' => 'O', 'ọ' => 'o',
'Ộ' => 'O', 'ộ' => 'o',
'Ợ' => 'O', 'ợ' => 'o',
'Ụ' => 'U', 'ụ' => 'u',
'Ự' => 'U', 'ự' => 'u',
'Ỵ' => 'Y', 'ỵ' => 'y',
// Vowels with diacritic (Chinese, Hanyu Pinyin)
'ɑ' => 'a',
// macron
'Ǖ' => 'U', 'ǖ' => 'u',
// acute accent
'Ǘ' => 'U', 'ǘ' => 'u',
// caron
'Ǎ' => 'A', 'ǎ' => 'a',
'Ǐ' => 'I', 'ǐ' => 'i',
'Ǒ' => 'O', 'ǒ' => 'o',
'Ǔ' => 'U', 'ǔ' => 'u',
'Ǚ' => 'U', 'ǚ' => 'u',
// grave accent
'Ǜ' => 'U', 'ǜ' => 'u',
);
private function removeAccents($string)
{
$chars = [
// Decompositions for Latin-1 Supplement
'ª' => 'a', 'º' => 'o',
'À' => 'A', 'Á' => 'A',
'Â' => 'A', 'Ã' => 'A',
'Ä' => 'A', 'Å' => 'A',
'Æ' => 'AE', 'Ç' => 'C',
'È' => 'E', 'É' => 'E',
'Ê' => 'E', 'Ë' => 'E',
'Ì' => 'I', 'Í' => 'I',
'Î' => 'I', 'Ï' => 'I',
'Ð' => 'D', 'Ñ' => 'N',
'Ò' => 'O', 'Ó' => 'O',
'Ô' => 'O', 'Õ' => 'O',
'Ö' => 'O', 'Ù' => 'U',
'Ú' => 'U', 'Û' => 'U',
'Ü' => 'U', 'Ý' => 'Y',
'Þ' => 'TH', 'ß' => 's',
'à' => 'a', 'á' => 'a',
'â' => 'a', 'ã' => 'a',
'ä' => 'a', 'å' => 'a',
'æ' => 'ae', 'ç' => 'c',
'è' => 'e', 'é' => 'e',
'ê' => 'e', 'ë' => 'e',
'ì' => 'i', 'í' => 'i',
'î' => 'i', 'ï' => 'i',
'ð' => 'd', 'ñ' => 'n',
'ò' => 'o', 'ó' => 'o',
'ô' => 'o', 'õ' => 'o',
'ö' => 'o', 'ø' => 'o',
'ù' => 'u', 'ú' => 'u',
'û' => 'u', 'ü' => 'u',
'ý' => 'y', 'þ' => 'th',
'ÿ' => 'y', 'Ø' => 'O',
// Decompositions for Latin Extended-A
'Ā' => 'A', 'ā' => 'a',
'Ă' => 'A', 'ă' => 'a',
'Ą' => 'A', 'ą' => 'a',
'Ć' => 'C', 'ć' => 'c',
'Ĉ' => 'C', 'ĉ' => 'c',
'Ċ' => 'C', 'ċ' => 'c',
'Č' => 'C', 'č' => 'c',
'Ď' => 'D', 'ď' => 'd',
'Đ' => 'D', 'đ' => 'd',
'Ē' => 'E', 'ē' => 'e',
'Ĕ' => 'E', 'ĕ' => 'e',
'Ė' => 'E', 'ė' => 'e',
'Ę' => 'E', 'ę' => 'e',
'Ě' => 'E', 'ě' => 'e',
'Ĝ' => 'G', 'ĝ' => 'g',
'Ğ' => 'G', 'ğ' => 'g',
'Ġ' => 'G', 'ġ' => 'g',
'Ģ' => 'G', 'ģ' => 'g',
'Ĥ' => 'H', 'ĥ' => 'h',
'Ħ' => 'H', 'ħ' => 'h',
'Ĩ' => 'I', 'ĩ' => 'i',
'Ī' => 'I', 'ī' => 'i',
'Ĭ' => 'I', 'ĭ' => 'i',
'Į' => 'I', 'į' => 'i',
'İ' => 'I', 'ı' => 'i',
'IJ' => 'IJ', 'ij' => 'ij',
'Ĵ' => 'J', 'ĵ' => 'j',
'Ķ' => 'K', 'ķ' => 'k',
'ĸ' => 'k', 'Ĺ' => 'L',
'ĺ' => 'l', 'Ļ' => 'L',
'ļ' => 'l', 'Ľ' => 'L',
'ľ' => 'l', 'Ŀ' => 'L',
'ŀ' => 'l', 'Ł' => 'L',
'ł' => 'l', 'Ń' => 'N',
'ń' => 'n', 'Ņ' => 'N',
'ņ' => 'n', 'Ň' => 'N',
'ň' => 'n', 'ʼn' => 'n',
'Ŋ' => 'N', 'ŋ' => 'n',
'Ō' => 'O', 'ō' => 'o',
'Ŏ' => 'O', 'ŏ' => 'o',
'Ő' => 'O', 'ő' => 'o',
'Œ' => 'OE', 'œ' => 'oe',
'Ŕ' => 'R', 'ŕ' => 'r',
'Ŗ' => 'R', 'ŗ' => 'r',
'Ř' => 'R', 'ř' => 'r',
'Ś' => 'S', 'ś' => 's',
'Ŝ' => 'S', 'ŝ' => 's',
'Ş' => 'S', 'ş' => 's',
'Š' => 'S', 'š' => 's',
'Ţ' => 'T', 'ţ' => 't',
'Ť' => 'T', 'ť' => 't',
'Ŧ' => 'T', 'ŧ' => 't',
'Ũ' => 'U', 'ũ' => 'u',
'Ū' => 'U', 'ū' => 'u',
'Ŭ' => 'U', 'ŭ' => 'u',
'Ů' => 'U', 'ů' => 'u',
'Ű' => 'U', 'ű' => 'u',
'Ų' => 'U', 'ų' => 'u',
'Ŵ' => 'W', 'ŵ' => 'w',
'Ŷ' => 'Y', 'ŷ' => 'y',
'Ÿ' => 'Y', 'Ź' => 'Z',
'ź' => 'z', 'Ż' => 'Z',
'ż' => 'z', 'Ž' => 'Z',
'ž' => 'z', 'ſ' => 's',
// Decompositions for Latin Extended-B
'Ș' => 'S', 'ș' => 's',
'Ț' => 'T', 'ț' => 't',
// Euro Sign
'€' => 'E',
// GBP (Pound) Sign
'£' => '',
// Vowels with diacritic (Vietnamese)
// unmarked
'Ơ' => 'O', 'ơ' => 'o',
'Ư' => 'U', 'ư' => 'u',
// grave accent
'Ầ' => 'A', 'ầ' => 'a',
'Ằ' => 'A', 'ằ' => 'a',
'Ề' => 'E', 'ề' => 'e',
'Ồ' => 'O', 'ồ' => 'o',
'Ờ' => 'O', 'ờ' => 'o',
'Ừ' => 'U', 'ừ' => 'u',
'Ỳ' => 'Y', 'ỳ' => 'y',
// hook
'Ả' => 'A', 'ả' => 'a',
'Ẩ' => 'A', 'ẩ' => 'a',
'Ẳ' => 'A', 'ẳ' => 'a',
'Ẻ' => 'E', 'ẻ' => 'e',
'Ể' => 'E', 'ể' => 'e',
'Ỉ' => 'I', 'ỉ' => 'i',
'Ỏ' => 'O', 'ỏ' => 'o',
'Ổ' => 'O', 'ổ' => 'o',
'Ở' => 'O', 'ở' => 'o',
'Ủ' => 'U', 'ủ' => 'u',
'Ử' => 'U', 'ử' => 'u',
'Ỷ' => 'Y', 'ỷ' => 'y',
// tilde
'Ẫ' => 'A', 'ẫ' => 'a',
'Ẵ' => 'A', 'ẵ' => 'a',
'Ẽ' => 'E', 'ẽ' => 'e',
'Ễ' => 'E', 'ễ' => 'e',
'Ỗ' => 'O', 'ỗ' => 'o',
'Ỡ' => 'O', 'ỡ' => 'o',
'Ữ' => 'U', 'ữ' => 'u',
'Ỹ' => 'Y', 'ỹ' => 'y',
// acute accent
'Ấ' => 'A', 'ấ' => 'a',
'Ắ' => 'A', 'ắ' => 'a',
'Ế' => 'E', 'ế' => 'e',
'Ố' => 'O', 'ố' => 'o',
'Ớ' => 'O', 'ớ' => 'o',
'Ứ' => 'U', 'ứ' => 'u',
// dot below
'Ạ' => 'A', 'ạ' => 'a',
'Ậ' => 'A', 'ậ' => 'a',
'Ặ' => 'A', 'ặ' => 'a',
'Ẹ' => 'E', 'ẹ' => 'e',
'Ệ' => 'E', 'ệ' => 'e',
'Ị' => 'I', 'ị' => 'i',
'Ọ' => 'O', 'ọ' => 'o',
'Ộ' => 'O', 'ộ' => 'o',
'Ợ' => 'O', 'ợ' => 'o',
'Ụ' => 'U', 'ụ' => 'u',
'Ự' => 'U', 'ự' => 'u',
'Ỵ' => 'Y', 'ỵ' => 'y',
// Vowels with diacritic (Chinese, Hanyu Pinyin)
'ɑ' => 'a',
// macron
'Ǖ' => 'U', 'ǖ' => 'u',
// acute accent
'Ǘ' => 'U', 'ǘ' => 'u',
// caron
'Ǎ' => 'A', 'ǎ' => 'a',
'Ǐ' => 'I', 'ǐ' => 'i',
'Ǒ' => 'O', 'ǒ' => 'o',
'Ǔ' => 'U', 'ǔ' => 'u',
'Ǚ' => 'U', 'ǚ' => 'u',
// grave accent
'Ǜ' => 'U', 'ǜ' => 'u',
];
$string = strtr($string, $chars);
$string = strtr($string, $chars);
return $string;
}
return $string;
}
}

View File

@@ -1,440 +1,432 @@
<?php
class BadDragonBridge extends BridgeAbstract {
const NAME = 'Bad Dragon Bridge';
const URI = 'https://bad-dragon.com/';
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Returns sales or new clearance items';
const MAINTAINER = 'Roliga';
const PARAMETERS = array(
'Sales' => array(
),
'Clearance' => array(
'ready_made' => array(
'name' => 'Ready Made',
'type' => 'checkbox'
),
'flop' => array(
'name' => 'Flops',
'type' => 'checkbox'
),
'skus' => array(
'name' => 'Products',
'exampleValue' => 'chanceflared, crackers',
'title' => 'Comma separated list of product SKUs'
),
'onesize' => array(
'name' => 'One-Size',
'type' => 'checkbox'
),
'mini' => array(
'name' => 'Mini',
'type' => 'checkbox'
),
'small' => array(
'name' => 'Small',
'type' => 'checkbox'
),
'medium' => array(
'name' => 'Medium',
'type' => 'checkbox'
),
'large' => array(
'name' => 'Large',
'type' => 'checkbox'
),
'extralarge' => array(
'name' => 'Extra Large',
'type' => 'checkbox'
),
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All' => 'all',
'Accessories' => 'accessories',
'Merchandise' => 'merchandise',
'Dildos' => 'insertable',
'Masturbators' => 'penetrable',
'Packers' => 'packer',
'Lil\' Squirts' => 'shooter',
'Lil\' Vibes' => 'vibrator',
'Wearables' => 'wearable'
),
'defaultValue' => 'all',
),
'soft' => array(
'name' => 'Soft Firmness',
'type' => 'checkbox'
),
'med_firm' => array(
'name' => 'Medium Firmness',
'type' => 'checkbox'
),
'firm' => array(
'name' => 'Firm',
'type' => 'checkbox'
),
'split' => array(
'name' => 'Split Firmness',
'type' => 'checkbox'
),
'maxprice' => array(
'name' => 'Max Price',
'type' => 'number',
'required' => true,
'defaultValue' => 300
),
'minprice' => array(
'name' => 'Min Price',
'type' => 'number',
'defaultValue' => 0
),
'cumtube' => array(
'name' => 'Cumtube',
'type' => 'checkbox'
),
'suctionCup' => array(
'name' => 'Suction Cup',
'type' => 'checkbox'
),
'noAccessories' => array(
'name' => 'No Accessories',
'type' => 'checkbox'
)
)
);
class BadDragonBridge extends BridgeAbstract
{
const NAME = 'Bad Dragon Bridge';
const URI = 'https://bad-dragon.com/';
const CACHE_TIMEOUT = 300; // 5min
const DESCRIPTION = 'Returns sales or new clearance items';
const MAINTAINER = 'Roliga';
const PARAMETERS = [
'Sales' => [
],
'Clearance' => [
'ready_made' => [
'name' => 'Ready Made',
'type' => 'checkbox'
],
'flop' => [
'name' => 'Flops',
'type' => 'checkbox'
],
'skus' => [
'name' => 'Products',
'exampleValue' => 'chanceflared, crackers',
'title' => 'Comma separated list of product SKUs'
],
'onesize' => [
'name' => 'One-Size',
'type' => 'checkbox'
],
'mini' => [
'name' => 'Mini',
'type' => 'checkbox'
],
'small' => [
'name' => 'Small',
'type' => 'checkbox'
],
'medium' => [
'name' => 'Medium',
'type' => 'checkbox'
],
'large' => [
'name' => 'Large',
'type' => 'checkbox'
],
'extralarge' => [
'name' => 'Extra Large',
'type' => 'checkbox'
],
'category' => [
'name' => 'Category',
'type' => 'list',
'values' => [
'All' => 'all',
'Accessories' => 'accessories',
'Merchandise' => 'merchandise',
'Dildos' => 'insertable',
'Masturbators' => 'penetrable',
'Packers' => 'packer',
'Lil\' Squirts' => 'shooter',
'Lil\' Vibes' => 'vibrator',
'Wearables' => 'wearable'
],
'defaultValue' => 'all',
],
'soft' => [
'name' => 'Soft Firmness',
'type' => 'checkbox'
],
'med_firm' => [
'name' => 'Medium Firmness',
'type' => 'checkbox'
],
'firm' => [
'name' => 'Firm',
'type' => 'checkbox'
],
'split' => [
'name' => 'Split Firmness',
'type' => 'checkbox'
],
'maxprice' => [
'name' => 'Max Price',
'type' => 'number',
'required' => true,
'defaultValue' => 300
],
'minprice' => [
'name' => 'Min Price',
'type' => 'number',
'defaultValue' => 0
],
'cumtube' => [
'name' => 'Cumtube',
'type' => 'checkbox'
],
'suctionCup' => [
'name' => 'Suction Cup',
'type' => 'checkbox'
],
'noAccessories' => [
'name' => 'No Accessories',
'type' => 'checkbox'
]
]
];
/*
* This sets index $strFrom (or $strTo if set) in $outArr to 'on' if
* $inArr[$param] contains $strFrom.
* It is used for translating BD's shop filter URLs into something we can use.
*
* For the query '?type[]=ready_made&type[]=flop' we would have an array like:
* Array (
* [type] => Array (
* [0] => ready_made
* [1] => flop
* )
* )
* which could be translated into:
* Array (
* [ready_made] => on
* [flop] => on
* )
* */
private function setParam($inArr, &$outArr, $param, $strFrom, $strTo = null) {
if(isset($inArr[$param]) && in_array($strFrom, $inArr[$param])) {
$outArr[($strTo ?: $strFrom)] = 'on';
}
}
/*
* This sets index $strFrom (or $strTo if set) in $outArr to 'on' if
* $inArr[$param] contains $strFrom.
* It is used for translating BD's shop filter URLs into something we can use.
*
* For the query '?type[]=ready_made&type[]=flop' we would have an array like:
* Array (
* [type] => Array (
* [0] => ready_made
* [1] => flop
* )
* )
* which could be translated into:
* Array (
* [ready_made] => on
* [flop] => on
* )
* */
private function setParam($inArr, &$outArr, $param, $strFrom, $strTo = null)
{
if (isset($inArr[$param]) && in_array($strFrom, $inArr[$param])) {
$outArr[($strTo ?: $strFrom)] = 'on';
}
}
public function detectParameters($url) {
$params = array();
public function detectParameters($url)
{
$params = [];
// Sale
$regex = '/^(https?:\/\/)?bad-dragon\.com\/sales/';
if(preg_match($regex, $url, $matches) > 0) {
return $params;
}
// Sale
$regex = '/^(https?:\/\/)?bad-dragon\.com\/sales/';
if (preg_match($regex, $url, $matches) > 0) {
return $params;
}
// Clearance
$regex = '/^(https?:\/\/)?bad-dragon\.com\/shop\/clearance/';
if(preg_match($regex, $url, $matches) > 0) {
parse_str(parse_url($url, PHP_URL_QUERY), $urlParams);
// Clearance
$regex = '/^(https?:\/\/)?bad-dragon\.com\/shop\/clearance/';
if (preg_match($regex, $url, $matches) > 0) {
parse_str(parse_url($url, PHP_URL_QUERY), $urlParams);
$this->setParam($urlParams, $params, 'type', 'ready_made');
$this->setParam($urlParams, $params, 'type', 'flop');
$this->setParam($urlParams, $params, 'type', 'ready_made');
$this->setParam($urlParams, $params, 'type', 'flop');
if(isset($urlParams['skus'])) {
$skus = array();
foreach($urlParams['skus'] as $sku) {
is_string($sku) && $skus[] = $sku;
is_array($sku) && $skus[] = $sku[0];
}
$params['skus'] = implode(',', $skus);
}
if (isset($urlParams['skus'])) {
$skus = [];
foreach ($urlParams['skus'] as $sku) {
is_string($sku) && $skus[] = $sku;
is_array($sku) && $skus[] = $sku[0];
}
$params['skus'] = implode(',', $skus);
}
$this->setParam($urlParams, $params, 'sizes', 'onesize');
$this->setParam($urlParams, $params, 'sizes', 'mini');
$this->setParam($urlParams, $params, 'sizes', 'small');
$this->setParam($urlParams, $params, 'sizes', 'medium');
$this->setParam($urlParams, $params, 'sizes', 'large');
$this->setParam($urlParams, $params, 'sizes', 'extralarge');
$this->setParam($urlParams, $params, 'sizes', 'onesize');
$this->setParam($urlParams, $params, 'sizes', 'mini');
$this->setParam($urlParams, $params, 'sizes', 'small');
$this->setParam($urlParams, $params, 'sizes', 'medium');
$this->setParam($urlParams, $params, 'sizes', 'large');
$this->setParam($urlParams, $params, 'sizes', 'extralarge');
if(isset($urlParams['category'])) {
$params['category'] = strtolower($urlParams['category']);
} else{
$params['category'] = 'all';
}
if (isset($urlParams['category'])) {
$params['category'] = strtolower($urlParams['category']);
} else {
$params['category'] = 'all';
}
$this->setParam($urlParams, $params, 'firmnessValues', 'soft');
$this->setParam($urlParams, $params, 'firmnessValues', 'medium', 'med_firm');
$this->setParam($urlParams, $params, 'firmnessValues', 'firm');
$this->setParam($urlParams, $params, 'firmnessValues', 'split');
$this->setParam($urlParams, $params, 'firmnessValues', 'soft');
$this->setParam($urlParams, $params, 'firmnessValues', 'medium', 'med_firm');
$this->setParam($urlParams, $params, 'firmnessValues', 'firm');
$this->setParam($urlParams, $params, 'firmnessValues', 'split');
if(isset($urlParams['price'])) {
isset($urlParams['price']['max'])
&& $params['maxprice'] = $urlParams['price']['max'];
isset($urlParams['price']['min'])
&& $params['minprice'] = $urlParams['price']['min'];
}
if (isset($urlParams['price'])) {
isset($urlParams['price']['max'])
&& $params['maxprice'] = $urlParams['price']['max'];
isset($urlParams['price']['min'])
&& $params['minprice'] = $urlParams['price']['min'];
}
isset($urlParams['cumtube'])
&& $urlParams['cumtube'] === '1'
&& $params['cumtube'] = 'on';
isset($urlParams['suctionCup'])
&& $urlParams['suctionCup'] === '1'
&& $params['suctionCup'] = 'on';
isset($urlParams['noAccessories'])
&& $urlParams['noAccessories'] === '1'
&& $params['noAccessories'] = 'on';
isset($urlParams['cumtube'])
&& $urlParams['cumtube'] === '1'
&& $params['cumtube'] = 'on';
isset($urlParams['suctionCup'])
&& $urlParams['suctionCup'] === '1'
&& $params['suctionCup'] = 'on';
isset($urlParams['noAccessories'])
&& $urlParams['noAccessories'] === '1'
&& $params['noAccessories'] = 'on';
return $params;
}
return $params;
}
return null;
}
return null;
}
public function getName() {
switch($this->queriedContext) {
case 'Sales':
return 'Bad Dragon Sales';
case 'Clearance':
return 'Bad Dragon Clearance Search';
default:
return parent::getName();
}
}
public function getName()
{
switch ($this->queriedContext) {
case 'Sales':
return 'Bad Dragon Sales';
case 'Clearance':
return 'Bad Dragon Clearance Search';
default:
return parent::getName();
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Sales':
return self::URI . 'sales';
case 'Clearance':
return $this->inputToURL();
default:
return parent::getURI();
}
}
public function getURI()
{
switch ($this->queriedContext) {
case 'Sales':
return self::URI . 'sales';
case 'Clearance':
return $this->inputToURL();
default:
return parent::getURI();
}
}
public function collectData() {
switch($this->queriedContext) {
case 'Sales':
$sales = json_decode(getContents(self::URI . 'api/sales'));
public function collectData()
{
switch ($this->queriedContext) {
case 'Sales':
$sales = json_decode(getContents(self::URI . 'api/sales'));
foreach($sales as $sale) {
$item = array();
foreach ($sales as $sale) {
$item = [];
$item['title'] = $sale->title;
$item['timestamp'] = strtotime($sale->startDate);
$item['title'] = $sale->title;
$item['timestamp'] = strtotime($sale->startDate);
$item['uri'] = $this->getURI() . '/' . $sale->slug;
$item['uri'] = $this->getURI() . '/' . $sale->slug;
$contentHTML = '<p><img src="' . $sale->image->url . '"></p>';
if(isset($sale->endDate)) {
$contentHTML .= '<p><b>This promotion ends on '
. gmdate('M j, Y \a\t g:i A T', strtotime($sale->endDate))
. '</b></p>';
} else {
$contentHTML .= '<p><b>This promotion never ends</b></p>';
}
$ul = false;
$content = json_decode($sale->content);
foreach($content->blocks as $block) {
switch($block->type) {
case 'header-one':
$contentHTML .= '<h1>' . $block->text . '</h1>';
break;
case 'header-two':
$contentHTML .= '<h2>' . $block->text . '</h2>';
break;
case 'header-three':
$contentHTML .= '<h3>' . $block->text . '</h3>';
break;
case 'unordered-list-item':
if(!$ul) {
$contentHTML .= '<ul>';
$ul = true;
}
$contentHTML .= '<li>' . $block->text . '</li>';
break;
default:
if($ul) {
$contentHTML .= '</ul>';
$ul = false;
}
$contentHTML .= '<p>' . $block->text . '</p>';
break;
}
}
$item['content'] = $contentHTML;
$contentHTML = '<p><img src="' . $sale->image->url . '"></p>';
if (isset($sale->endDate)) {
$contentHTML .= '<p><b>This promotion ends on '
. gmdate('M j, Y \a\t g:i A T', strtotime($sale->endDate))
. '</b></p>';
} else {
$contentHTML .= '<p><b>This promotion never ends</b></p>';
}
$ul = false;
$content = json_decode($sale->content);
foreach ($content->blocks as $block) {
switch ($block->type) {
case 'header-one':
$contentHTML .= '<h1>' . $block->text . '</h1>';
break;
case 'header-two':
$contentHTML .= '<h2>' . $block->text . '</h2>';
break;
case 'header-three':
$contentHTML .= '<h3>' . $block->text . '</h3>';
break;
case 'unordered-list-item':
if (!$ul) {
$contentHTML .= '<ul>';
$ul = true;
}
$contentHTML .= '<li>' . $block->text . '</li>';
break;
default:
if ($ul) {
$contentHTML .= '</ul>';
$ul = false;
}
$contentHTML .= '<p>' . $block->text . '</p>';
break;
}
}
$item['content'] = $contentHTML;
$this->items[] = $item;
}
break;
case 'Clearance':
$toyData = json_decode(getContents($this->inputToURL(true)));
$this->items[] = $item;
}
break;
case 'Clearance':
$toyData = json_decode(getContents($this->inputToURL(true)));
$productList = json_decode(getContents(self::URI
. 'api/inventory-toy/product-list'));
$productList = json_decode(getContents(self::URI
. 'api/inventory-toy/product-list'));
foreach($toyData->toys as $toy) {
$item = array();
foreach ($toyData->toys as $toy) {
$item = [];
$item['uri'] = $this->getURI()
. '#'
. $toy->id;
$item['timestamp'] = strtotime($toy->created);
$item['uri'] = $this->getURI()
. '#'
. $toy->id;
$item['timestamp'] = strtotime($toy->created);
foreach($productList as $product) {
if($product->sku == $toy->sku) {
$item['title'] = $product->name;
break;
}
}
foreach ($productList as $product) {
if ($product->sku == $toy->sku) {
$item['title'] = $product->name;
break;
}
}
// images
$content = '<p>';
foreach($toy->images as $image) {
$content .= '<a href="'
. $image->fullFilename
. '"><img src="'
. $image->thumbFilename
. '" /></a>';
}
// price
$content .= '</p><p><b>Price:</b> $'
. $toy->price
// size
. '<br /><b>Size:</b> '
. $toy->size
// color
. '<br /><b>Color:</b> '
. $toy->color
// features
. '<br /><b>Features:</b> '
. ($toy->suction_cup ? 'Suction cup' : '')
. ($toy->suction_cup && $toy->cumtube ? ', ' : '')
. ($toy->cumtube ? 'Cumtube' : '')
. ($toy->suction_cup || $toy->cumtube ? '' : 'None');
// firmness
$firmnessTexts = array(
'2' => 'Extra soft',
'3' => 'Soft',
'5' => 'Medium',
'8' => 'Firm'
);
$firmnesses = explode('/', $toy->firmness);
if(count($firmnesses) === 2) {
$content .= '<br /><b>Firmness:</b> '
. $firmnessTexts[$firmnesses[0]]
. ', '
. $firmnessTexts[$firmnesses[1]];
} else{
$content .= '<br /><b>Firmness:</b> '
. $firmnessTexts[$firmnesses[0]];
}
// flop
if($toy->type === 'flop') {
$content .= '<br /><b>Flop reason:</b> '
. $toy->flop_reason;
}
$content .= '</p>';
$item['content'] = $content;
// images
$content = '<p>';
foreach ($toy->images as $image) {
$content .= '<a href="'
. $image->fullFilename
. '"><img src="'
. $image->thumbFilename
. '" /></a>';
}
// price
$content .= '</p><p><b>Price:</b> $'
. $toy->price
// size
. '<br /><b>Size:</b> '
. $toy->size
// color
. '<br /><b>Color:</b> '
. $toy->color
// features
. '<br /><b>Features:</b> '
. ($toy->suction_cup ? 'Suction cup' : '')
. ($toy->suction_cup && $toy->cumtube ? ', ' : '')
. ($toy->cumtube ? 'Cumtube' : '')
. ($toy->suction_cup || $toy->cumtube ? '' : 'None');
// firmness
$firmnessTexts = [
'2' => 'Extra soft',
'3' => 'Soft',
'5' => 'Medium',
'8' => 'Firm'
];
$firmnesses = explode('/', $toy->firmness);
if (count($firmnesses) === 2) {
$content .= '<br /><b>Firmness:</b> '
. $firmnessTexts[$firmnesses[0]]
. ', '
. $firmnessTexts[$firmnesses[1]];
} else {
$content .= '<br /><b>Firmness:</b> '
. $firmnessTexts[$firmnesses[0]];
}
// flop
if ($toy->type === 'flop') {
$content .= '<br /><b>Flop reason:</b> '
. $toy->flop_reason;
}
$content .= '</p>';
$item['content'] = $content;
$enclosures = array();
foreach($toy->images as $image) {
$enclosures[] = $image->fullFilename;
}
$item['enclosures'] = $enclosures;
$enclosures = [];
foreach ($toy->images as $image) {
$enclosures[] = $image->fullFilename;
}
$item['enclosures'] = $enclosures;
$categories = array();
$categories[] = $toy->sku;
$categories[] = $toy->type;
$categories[] = $toy->size;
if($toy->cumtube) {
$categories[] = 'cumtube';
}
if($toy->suction_cup) {
$categories[] = 'suction_cup';
}
$item['categories'] = $categories;
$categories = [];
$categories[] = $toy->sku;
$categories[] = $toy->type;
$categories[] = $toy->size;
if ($toy->cumtube) {
$categories[] = 'cumtube';
}
if ($toy->suction_cup) {
$categories[] = 'suction_cup';
}
$item['categories'] = $categories;
$this->items[] = $item;
}
break;
}
}
$this->items[] = $item;
}
break;
}
}
private function inputToURL($api = false) {
$url = self::URI;
$url .= ($api ? 'api/inventory-toys?' : 'shop/clearance?');
private function inputToURL($api = false)
{
$url = self::URI;
$url .= ($api ? 'api/inventory-toys?' : 'shop/clearance?');
// Default parameters
$url .= 'limit=60';
$url .= '&page=1';
$url .= '&sort[field]=created';
$url .= '&sort[direction]=desc';
// Default parameters
$url .= 'limit=60';
$url .= '&page=1';
$url .= '&sort[field]=created';
$url .= '&sort[direction]=desc';
// Product types
$url .= ($this->getInput('ready_made') ? '&type[]=ready_made' : '');
$url .= ($this->getInput('flop') ? '&type[]=flop' : '');
// Product types
$url .= ($this->getInput('ready_made') ? '&type[]=ready_made' : '');
$url .= ($this->getInput('flop') ? '&type[]=flop' : '');
// Product names
foreach(array_filter(explode(',', $this->getInput('skus'))) as $sku) {
$url .= '&skus[]=' . urlencode(trim($sku));
}
// Product names
foreach (array_filter(explode(',', $this->getInput('skus'))) as $sku) {
$url .= '&skus[]=' . urlencode(trim($sku));
}
// Size
$url .= ($this->getInput('onesize') ? '&sizes[]=onesize' : '');
$url .= ($this->getInput('mini') ? '&sizes[]=mini' : '');
$url .= ($this->getInput('small') ? '&sizes[]=small' : '');
$url .= ($this->getInput('medium') ? '&sizes[]=medium' : '');
$url .= ($this->getInput('large') ? '&sizes[]=large' : '');
$url .= ($this->getInput('extralarge') ? '&sizes[]=extralarge' : '');
// Size
$url .= ($this->getInput('onesize') ? '&sizes[]=onesize' : '');
$url .= ($this->getInput('mini') ? '&sizes[]=mini' : '');
$url .= ($this->getInput('small') ? '&sizes[]=small' : '');
$url .= ($this->getInput('medium') ? '&sizes[]=medium' : '');
$url .= ($this->getInput('large') ? '&sizes[]=large' : '');
$url .= ($this->getInput('extralarge') ? '&sizes[]=extralarge' : '');
// Category
$url .= ($this->getInput('category') ? '&category='
. urlencode($this->getInput('category')) : '');
// Category
$url .= ($this->getInput('category') ? '&category='
. urlencode($this->getInput('category')) : '');
// Firmness
if($api) {
$url .= ($this->getInput('soft') ? '&firmnessValues[]=3' : '');
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=5' : '');
$url .= ($this->getInput('firm') ? '&firmnessValues[]=8' : '');
if($this->getInput('split')) {
$url .= '&firmnessValues[]=3/5';
$url .= '&firmnessValues[]=3/8';
$url .= '&firmnessValues[]=8/3';
$url .= '&firmnessValues[]=5/8';
$url .= '&firmnessValues[]=8/5';
}
} else{
$url .= ($this->getInput('soft') ? '&firmnessValues[]=soft' : '');
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=medium' : '');
$url .= ($this->getInput('firm') ? '&firmnessValues[]=firm' : '');
$url .= ($this->getInput('split') ? '&firmnessValues[]=split' : '');
}
// Firmness
if ($api) {
$url .= ($this->getInput('soft') ? '&firmnessValues[]=3' : '');
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=5' : '');
$url .= ($this->getInput('firm') ? '&firmnessValues[]=8' : '');
if ($this->getInput('split')) {
$url .= '&firmnessValues[]=3/5';
$url .= '&firmnessValues[]=3/8';
$url .= '&firmnessValues[]=8/3';
$url .= '&firmnessValues[]=5/8';
$url .= '&firmnessValues[]=8/5';
}
} else {
$url .= ($this->getInput('soft') ? '&firmnessValues[]=soft' : '');
$url .= ($this->getInput('med_firm') ? '&firmnessValues[]=medium' : '');
$url .= ($this->getInput('firm') ? '&firmnessValues[]=firm' : '');
$url .= ($this->getInput('split') ? '&firmnessValues[]=split' : '');
}
// Price
$url .= ($this->getInput('maxprice') ? '&price[max]='
. $this->getInput('maxprice') : '&price[max]=300');
$url .= ($this->getInput('minprice') ? '&price[min]='
. $this->getInput('minprice') : '&price[min]=0');
// Price
$url .= ($this->getInput('maxprice') ? '&price[max]='
. $this->getInput('maxprice') : '&price[max]=300');
$url .= ($this->getInput('minprice') ? '&price[min]='
. $this->getInput('minprice') : '&price[min]=0');
// Features
$url .= ($this->getInput('cumtube') ? '&cumtube=1' : '');
$url .= ($this->getInput('suctionCup') ? '&suctionCup=1' : '');
$url .= ($this->getInput('noAccessories') ? '&noAccessories=1' : '');
// Features
$url .= ($this->getInput('cumtube') ? '&cumtube=1' : '');
$url .= ($this->getInput('suctionCup') ? '&suctionCup=1' : '');
$url .= ($this->getInput('noAccessories') ? '&noAccessories=1' : '');
return $url;
}
return $url;
}
}

View File

@@ -1,211 +1,186 @@
<?php
class BakaUpdatesMangaReleasesBridge extends BridgeAbstract {
const NAME = 'Baka Updates Manga Releases';
const URI = 'https://www.mangaupdates.com/';
const DESCRIPTION = 'Get the latest series releases';
const MAINTAINER = 'fulmeek, KamaleiZestri';
const PARAMETERS = array(
'By series' => array(
'series_id' => array(
'name' => 'Series ID',
'type' => 'number',
'required' => true,
'exampleValue' => '188066'
)
),
'By list' => array(
'list_id' => array(
'name' => 'List ID and Type',
'type' => 'text',
'required' => true,
'exampleValue' => '4395&list=read'
)
)
);
const LIMIT_COLS = 5;
const LIMIT_ITEMS = 10;
const RELEASES_URL = 'https://www.mangaupdates.com/releases.html';
class BakaUpdatesMangaReleasesBridge extends BridgeAbstract
{
const NAME = 'Baka Updates Manga Releases';
const URI = 'https://www.mangaupdates.com/';
const DESCRIPTION = 'Get the latest series releases';
const MAINTAINER = 'fulmeek, KamaleiZestri';
const PARAMETERS = [
'By series' => [
'series_id' => [
'name' => 'Series ID',
'type' => 'number',
'required' => true,
'exampleValue' => '188066'
]
],
'By list' => [
'list_id' => [
'name' => 'List ID and Type',
'type' => 'text',
'required' => true,
'exampleValue' => '4395&list=read'
]
]
];
const LIMIT_COLS = 5;
const LIMIT_ITEMS = 10;
const RELEASES_URL = 'https://www.mangaupdates.com/releases.html';
private $feedName = '';
private $feedName = '';
public function collectData() {
if($this -> queriedContext == 'By series')
$this -> collectDataBySeries();
else //queriedContext == 'By list'
$this -> collectDataByList();
}
public function collectData()
{
if ($this -> queriedContext == 'By series') {
$this -> collectDataBySeries();
} else { //queriedContext == 'By list'
$this -> collectDataByList();
}
}
public function getURI(){
if($this -> queriedContext == 'By series') {
$series_id = $this->getInput('series_id');
if (!empty($series_id)) {
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
}
} else //queriedContext == 'By list'
return self::RELEASES_URL;
public function getURI()
{
if ($this -> queriedContext == 'By series') {
$series_id = $this->getInput('series_id');
if (!empty($series_id)) {
return self::URI . 'releases.html?search=' . $series_id . '&stype=series';
}
} else { //queriedContext == 'By list'
return self::RELEASES_URL;
}
return self::URI;
}
return self::URI;
}
public function getName(){
if(!empty($this->feedName)) {
return $this->feedName . ' - ' . self::NAME;
}
return parent::getName();
}
public function getName()
{
if (!empty($this->feedName)) {
return $this->feedName . ' - ' . self::NAME;
}
return parent::getName();
}
private function getSanitizedHash($string) {
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
}
private function getSanitizedHash($string)
{
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
}
private function filterText($text) {
return rtrim($text, '* ');
}
private function filterText($text)
{
return rtrim($text, '* ');
}
private function filterHTML($text) {
return $this->filterText(html_entity_decode($text));
}
private function filterHTML($text)
{
return $this->filterText(html_entity_decode($text));
}
private function findID($manga) {
// sometimes new series are on the release list that have no ID. just drop them.
if(@$this -> filterHTML($manga -> find('a', 0) -> href) != null) {
preg_match('/id=([0-9]*)/', $this -> filterHTML($manga -> find('a', 0) -> href), $match);
return $match[1];
} else
return 0;
}
private function findID($manga)
{
// sometimes new series are on the release list that have no ID. just drop them.
if (@$this -> filterHTML($manga -> find('a', 0) -> href) != null) {
preg_match('/id=([0-9]*)/', $this -> filterHTML($manga -> find('a', 0) -> href), $match);
return $match[1];
} else {
return 0;
}
}
private function collectDataBySeries() {
$html = getSimpleHTMLDOM($this->getURI());
private function collectDataBySeries()
{
$html = getSimpleHTMLDOM($this->getURI());
// content is an unstructured pile of divs, ugly to parse
$cols = $html->find('div#main_content div.row > div.text');
if (!$cols)
returnServerError('No releases');
// content is an unstructured pile of divs, ugly to parse
$cols = $html->find('div#main_content div.row > div.text');
if (!$cols) {
returnServerError('No releases');
}
$rows = array_slice(
array_chunk($cols, self::LIMIT_COLS), 0, self::LIMIT_ITEMS
);
$rows = array_slice(
array_chunk($cols, self::LIMIT_COLS),
0,
self::LIMIT_ITEMS
);
if (isset($rows[0][1])) {
$this->feedName = $this->filterHTML($rows[0][1]->plaintext);
}
if (isset($rows[0][1])) {
$this->feedName = $this->filterHTML($rows[0][1]->plaintext);
}
foreach($rows as $cols) {
if (count($cols) < self::LIMIT_COLS) continue;
foreach ($rows as $cols) {
if (count($cols) < self::LIMIT_COLS) {
continue;
}
$item = array();
$title = array();
$item = [];
$title = [];
$item['content'] = '';
$item['content'] = '';
$objDate = $cols[0];
if ($objDate)
$item['timestamp'] = strtotime($objDate->plaintext);
$objDate = $cols[0];
if ($objDate) {
$item['timestamp'] = strtotime($objDate->plaintext);
}
$objTitle = $cols[1];
if ($objTitle) {
$title[] = $this->filterHTML($objTitle->plaintext);
$item['content'] .= '<p>Series: ' . $this->filterText($objTitle->innertext) . '</p>';
}
$objTitle = $cols[1];
if ($objTitle) {
$title[] = $this->filterHTML($objTitle->plaintext);
$item['content'] .= '<p>Series: ' . $this->filterText($objTitle->innertext) . '</p>';
}
$objVolume = $cols[2];
if ($objVolume && !empty($objVolume->plaintext))
$title[] = 'Vol.' . $objVolume->plaintext;
$objVolume = $cols[2];
if ($objVolume && !empty($objVolume->plaintext)) {
$title[] = 'Vol.' . $objVolume->plaintext;
}
$objChapter = $cols[3];
if ($objChapter && !empty($objChapter->plaintext))
$title[] = 'Chp.' . $objChapter->plaintext;
$objChapter = $cols[3];
if ($objChapter && !empty($objChapter->plaintext)) {
$title[] = 'Chp.' . $objChapter->plaintext;
}
$objAuthor = $cols[4];
if ($objAuthor && !empty($objAuthor->plaintext)) {
$item['author'] = $this->filterHTML($objAuthor->plaintext);
$item['content'] .= '<p>Groups: ' . $this->filterText($objAuthor->innertext) . '</p>';
}
$objAuthor = $cols[4];
if ($objAuthor && !empty($objAuthor->plaintext)) {
$item['author'] = $this->filterHTML($objAuthor->plaintext);
$item['content'] .= '<p>Groups: ' . $this->filterText($objAuthor->innertext) . '</p>';
}
$item['title'] = implode(' ', $title);
$item['uri'] = $this->getURI();
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
$item['title'] = implode(' ', $title);
$item['uri'] = $this->getURI();
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
private function collectDataByList() {
$this -> feedName = 'Releases';
$list = array();
private function collectDataByList()
{
$this -> feedName = 'Releases';
$list = [];
$releasesHTML = getSimpleHTMLDOM(self::RELEASES_URL);
$releasesHTML = getSimpleHTMLDOM(self::RELEASES_URL);
$list_id = $this -> getInput('list_id');
$listHTML = getSimpleHTMLDOM('https://www.mangaupdates.com/mylist.html?id=' . $list_id);
$list_id = $this -> getInput('list_id');
$listHTML = getSimpleHTMLDOM('https://www.mangaupdates.com/mylist.html?id=' . $list_id);
//get ids of the manga that the user follows,
$parts = $listHTML -> find('table#ptable tr > td.pl');
foreach($parts as $part) {
$list[] = $this -> findID($part);
}
//get ids of the manga that the user follows,
$parts = $listHTML -> find('table#ptable tr > td.pl');
foreach ($parts as $part) {
$list[] = $this -> findID($part);
}
//similar to above, but the divs are in groups of 3.
$cols = $releasesHTML -> find('div#main_content div.row > div.pbreak');
$rows = array_slice(array_chunk($cols, 3), 0);
//similar to above, but the divs are in groups of 3.
$cols = $releasesHTML -> find('div#main_content div.row > div.pbreak');
$rows = array_slice(array_chunk($cols, 3), 0);
foreach($rows as $cols) {
//check if current manga is in user's list.
$id = $this -> findId($cols[0]);
if(!array_search($id, $list)) continue;
foreach ($rows as $cols) {
//check if current manga is in user's list.
$id = $this -> findId($cols[0]);
if (!array_search($id, $list)) {
continue;
}
$item = array();
$title = array();
$item = [];
$title = [];
$item['content'] = '';
$item['content'] = '';
$objTitle = $cols[0];
if ($objTitle) {
$title[] = $this->filterHTML($objTitle->plaintext);
$item['content'] .= '<p>Series: ' . $this->filterHTML($objTitle -> innertext) . '</p>';
}
$objTitle = $cols[0];
if ($objTitle) {
$title[] = $this->filterHTML($objTitle->plaintext);
$item['content'] .= '<p>Series: ' . $this->filterHTML($objTitle -> innertext) . '</p>';
}
$objVolChap = $cols[1];
if ($objVolChap && !empty($objVolChap->plaintext))
$title[] = $this -> filterHTML($objVolChap -> innertext);
$objVolChap = $cols[1];
if ($objVolChap && !empty($objVolChap->plaintext)) {
$title[] = $this -> filterHTML($objVolChap -> innertext);
}
$objAuthor = $cols[2];
if ($objAuthor && !empty($objAuthor->plaintext)) {
$item['author'] = $this->filterHTML($objAuthor -> plaintext);
$item['content'] .= '<p>Groups: ' . $this->filterHTML($objAuthor -> innertext) . '</p>';
}
$objAuthor = $cols[2];
if ($objAuthor && !empty($objAuthor->plaintext)) {
$item['author'] = $this->filterHTML($objAuthor -> plaintext);
$item['content'] .= '<p>Groups: ' . $this->filterHTML($objAuthor -> innertext) . '</p>';
}
$item['title'] = implode(' ', $title);
$item['uri'] = self::URI . 'releases.html?search=' . $id . '&stype=series';
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
$item['title'] = implode(' ', $title);
$item['uri'] = self::URI . 'releases.html?search=' . $id . '&stype=series';
$item['uid'] = $this->getSanitizedHash($item['title'] . $item['author']);
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
}

View File

@@ -1,421 +1,408 @@
<?php
class BandcampBridge extends BridgeAbstract {
class BandcampBridge extends BridgeAbstract
{
const MAINTAINER = 'sebsauvage, Roliga';
const NAME = 'Bandcamp Bridge';
const URI = 'https://bandcamp.com/';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'New bandcamp releases by tag, band or album';
const PARAMETERS = [
'By tag' => [
'tag' => [
'name' => 'tag',
'type' => 'text',
'required' => true,
'exampleValue' => 'hip-hop-rap'
]
],
'By band' => [
'band' => [
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the band page URL',
'required' => true,
'exampleValue' => 'aesoprock'
],
'type' => [
'name' => 'Articles are',
'type' => 'list',
'values' => [
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
],
'defaultValue' => 'changes'
],
'limit' => [
'name' => 'limit',
'type' => 'number',
'required' => true,
'title' => 'Number of releases to return',
'defaultValue' => 5
]
],
'By label' => [
'label' => [
'name' => 'label',
'type' => 'text',
'title' => 'label name as seen in the label page URL',
'required' => true
],
'type' => [
'name' => 'Articles are',
'type' => 'list',
'values' => [
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
],
'defaultValue' => 'changes'
],
'limit' => [
'name' => 'limit',
'type' => 'number',
'title' => 'Number of releases to return',
'defaultValue' => 5
]
],
'By album' => [
'band' => [
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the album page URL',
'required' => true,
'exampleValue' => 'aesoprock'
],
'album' => [
'name' => 'album',
'type' => 'text',
'title' => 'Album name as seen in the album page URL',
'required' => true,
'exampleValue' => 'appleseed'
],
'type' => [
'name' => 'Articles are',
'type' => 'list',
'values' => [
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
],
'defaultValue' => 'tracks'
]
]
];
const IMGURI = 'https://f4.bcbits.com/';
const IMGSIZE_300PX = 23;
const IMGSIZE_700PX = 16;
const MAINTAINER = 'sebsauvage, Roliga';
const NAME = 'Bandcamp Bridge';
const URI = 'https://bandcamp.com/';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'New bandcamp releases by tag, band or album';
const PARAMETERS = array(
'By tag' => array(
'tag' => array(
'name' => 'tag',
'type' => 'text',
'required' => true,
'exampleValue' => 'hip-hop-rap'
)
),
'By band' => array(
'band' => array(
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the band page URL',
'required' => true,
'exampleValue' => 'aesoprock'
),
'type' => array(
'name' => 'Articles are',
'type' => 'list',
'values' => array(
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
),
'defaultValue' => 'changes'
),
'limit' => array(
'name' => 'limit',
'type' => 'number',
'required' => true,
'title' => 'Number of releases to return',
'defaultValue' => 5
)
),
'By label' => array(
'label' => array(
'name' => 'label',
'type' => 'text',
'title' => 'label name as seen in the label page URL',
'required' => true
),
'type' => array(
'name' => 'Articles are',
'type' => 'list',
'values' => array(
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
),
'defaultValue' => 'changes'
),
'limit' => array(
'name' => 'limit',
'type' => 'number',
'title' => 'Number of releases to return',
'defaultValue' => 5
)
),
'By album' => array(
'band' => array(
'name' => 'band',
'type' => 'text',
'title' => 'Band name as seen in the album page URL',
'required' => true,
'exampleValue' => 'aesoprock'
),
'album' => array(
'name' => 'album',
'type' => 'text',
'title' => 'Album name as seen in the album page URL',
'required' => true,
'exampleValue' => 'appleseed'
),
'type' => array(
'name' => 'Articles are',
'type' => 'list',
'values' => array(
'Releases' => 'releases',
'Releases, new one when track list changes' => 'changes',
'Individual tracks' => 'tracks'
),
'defaultValue' => 'tracks'
)
)
);
const IMGURI = 'https://f4.bcbits.com/';
const IMGSIZE_300PX = 23;
const IMGSIZE_700PX = 16;
private $feedName;
private $feedName;
public function getIcon()
{
return 'https://s4.bcbits.com/img/bc_favicon.ico';
}
public function getIcon() {
return 'https://s4.bcbits.com/img/bc_favicon.ico';
}
public function collectData()
{
switch ($this->queriedContext) {
case 'By tag':
$url = self::URI . 'api/hub/1/dig_deeper';
$data = $this->buildRequestJson();
$header = [
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
];
$opts = [
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $data
];
$content = getContents($url, $header, $opts);
public function collectData(){
switch($this->queriedContext) {
case 'By tag':
$url = self::URI . 'api/hub/1/dig_deeper';
$data = $this->buildRequestJson();
$header = array(
'Content-Type: application/json',
'Content-Length: ' . strlen($data)
);
$opts = array(
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_POSTFIELDS => $data
);
$content = getContents($url, $header, $opts);
$json = json_decode($content);
$json = json_decode($content);
if ($json->ok !== true) {
returnServerError('Invalid response');
}
if ($json->ok !== true) {
returnServerError('Invalid response');
}
foreach ($json->items as $entry) {
$url = $entry->tralbum_url;
$artist = $entry->artist;
$title = $entry->title;
// e.g. record label is the releaser, but not the artist
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
foreach ($json->items as $entry) {
$url = $entry->tralbum_url;
$artist = $entry->artist;
$title = $entry->title;
// e.g. record label is the releaser, but not the artist
$releaser = $entry->band_name !== $entry->artist ? $entry->band_name : null;
$full_title = $artist . ' - ' . $title;
$full_artist = $artist;
if (isset($releaser)) {
$full_title .= ' (' . $releaser . ')';
$full_artist .= ' (' . $releaser . ')';
}
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
$full_title = $artist . ' - ' . $title;
$full_artist = $artist;
if (isset($releaser)) {
$full_title .= ' (' . $releaser . ')';
$full_artist .= ' (' . $releaser . ')';
}
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
$item = [
'uri' => $url,
'author' => $full_artist,
'title' => $full_title
];
$item['content'] = "<img src='$small_img' /><br/>$full_title";
$item['enclosures'] = [$img];
$this->items[] = $item;
}
break;
case 'By band':
case 'By label':
case 'By album':
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
$item = array(
'uri' => $url,
'author' => $full_artist,
'title' => $full_title
);
$item['content'] = "<img src='$small_img' /><br/>$full_title";
$item['enclosures'] = array($img);
$this->items[] = $item;
}
break;
case 'By band':
case 'By label':
case 'By album':
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
if ($html->find('meta[name=title]', 0)) {
$this->feedName = $html->find('meta[name=title]', 0)->content;
} else {
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
}
if ($html->find('meta[name=title]', 0)) {
$this->feedName = $html->find('meta[name=title]', 0)->content;
} else {
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
}
$regex = '/band_id=(\d+)/';
if (preg_match($regex, $html, $matches) == false) {
returnServerError('Unable to find band ID on: ' . $this->getURI());
}
$band_id = $matches[1];
$regex = '/band_id=(\d+)/';
if(preg_match($regex, $html, $matches) == false)
returnServerError('Unable to find band ID on: ' . $this->getURI());
$band_id = $matches[1];
$tralbums = [];
switch ($this->queriedContext) {
case 'By band':
case 'By label':
$query_data = [
'band_id' => $band_id
];
$band_data = $this->apiGet('mobile/22/band_details', $query_data);
$tralbums = array();
switch($this->queriedContext) {
case 'By band':
case 'By label':
$query_data = array(
'band_id' => $band_id
);
$band_data = $this->apiGet('mobile/22/band_details', $query_data);
$num_albums = min(count($band_data->discography), $this->getInput('limit'));
for ($i = 0; $i < $num_albums; $i++) {
$album_basic_data = $band_data->discography[$i];
$num_albums = min(count($band_data->discography), $this->getInput('limit'));
for($i = 0; $i < $num_albums; $i++) {
$album_basic_data = $band_data->discography[$i];
// 'a' or 't' for albums and individual tracks respectively
$tralbum_type = substr($album_basic_data->item_type, 0, 1);
// 'a' or 't' for albums and individual tracks respectively
$tralbum_type = substr($album_basic_data->item_type, 0, 1);
$query_data = [
'band_id' => $band_id,
'tralbum_type' => $tralbum_type,
'tralbum_id' => $album_basic_data->item_id
];
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
}
break;
case 'By album':
$regex = '/album=(\d+)/';
if (preg_match($regex, $html, $matches) == false) {
returnServerError('Unable to find album ID on: ' . $this->getURI());
}
$album_id = $matches[1];
$query_data = array(
'band_id' => $band_id,
'tralbum_type' => $tralbum_type,
'tralbum_id' => $album_basic_data->item_id
);
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
}
break;
case 'By album':
$regex = '/album=(\d+)/';
if(preg_match($regex, $html, $matches) == false)
returnServerError('Unable to find album ID on: ' . $this->getURI());
$album_id = $matches[1];
$query_data = [
'band_id' => $band_id,
'tralbum_type' => 'a',
'tralbum_id' => $album_id
];
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
$query_data = array(
'band_id' => $band_id,
'tralbum_type' => 'a',
'tralbum_id' => $album_id
);
$tralbums[] = $this->apiGet('mobile/22/tralbum_details', $query_data);
break;
}
break;
}
foreach ($tralbums as $tralbum_data) {
if ($tralbum_data->type === 'a' && $this->getInput('type') === 'tracks') {
foreach ($tralbum_data->tracks as $track) {
$query_data = [
'band_id' => $band_id,
'tralbum_type' => 't',
'tralbum_id' => $track->track_id
];
$track_data = $this->apiGet('mobile/22/tralbum_details', $query_data);
foreach ($tralbums as $tralbum_data) {
if ($tralbum_data->type === 'a' && $this->getInput('type') === 'tracks') {
foreach ($tralbum_data->tracks as $track) {
$query_data = array(
'band_id' => $band_id,
'tralbum_type' => 't',
'tralbum_id' => $track->track_id
);
$track_data = $this->apiGet('mobile/22/tralbum_details', $query_data);
$this->items[] = $this->buildTralbumItem($track_data);
}
} else {
$this->items[] = $this->buildTralbumItem($tralbum_data);
}
}
break;
}
}
$this->items[] = $this->buildTralbumItem($track_data);
}
} else {
$this->items[] = $this->buildTralbumItem($tralbum_data);
}
}
break;
}
}
private function buildTralbumItem($tralbum_data)
{
$band_data = $tralbum_data->band;
private function buildTralbumItem($tralbum_data){
$band_data = $tralbum_data->band;
// Format title like: ARTIST - ALBUM/TRACK (OPTIONAL RELEASER)
// Format artist/author like: ARTIST (OPTIONAL RELEASER)
//
// If the album/track is released under a label/a band other than the artist
// themselves, append that releaser name to the title and artist/author.
//
// This sadly doesn't always work right for individual tracks as the artist
// of the track is always set to the releaser.
$artist = $tralbum_data->tralbum_artist;
$full_title = $artist . ' - ' . $tralbum_data->title;
$full_artist = $artist;
if (isset($tralbum_data->label)) {
$full_title .= ' (' . $tralbum_data->label . ')';
$full_artist .= ' (' . $tralbum_data->label . ')';
} elseif ($band_data->name !== $artist) {
$full_title .= ' (' . $band_data->name . ')';
$full_artist .= ' (' . $band_data->name . ')';
}
// Format title like: ARTIST - ALBUM/TRACK (OPTIONAL RELEASER)
// Format artist/author like: ARTIST (OPTIONAL RELEASER)
//
// If the album/track is released under a label/a band other than the artist
// themselves, append that releaser name to the title and artist/author.
//
// This sadly doesn't always work right for individual tracks as the artist
// of the track is always set to the releaser.
$artist = $tralbum_data->tralbum_artist;
$full_title = $artist . ' - ' . $tralbum_data->title;
$full_artist = $artist;
if (isset($tralbum_data->label)) {
$full_title .= ' (' . $tralbum_data->label . ')';
$full_artist .= ' (' . $tralbum_data->label . ')';
} elseif ($band_data->name !== $artist) {
$full_title .= ' (' . $band_data->name . ')';
$full_artist .= ' (' . $band_data->name . ')';
}
$small_img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_700PX);
$small_img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_300PX);
$img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_700PX);
$item = [
'uri' => $tralbum_data->bandcamp_url,
'author' => $full_artist,
'title' => $full_title,
'enclosures' => [$img],
'timestamp' => $tralbum_data->release_date
];
$item = array(
'uri' => $tralbum_data->bandcamp_url,
'author' => $full_artist,
'title' => $full_title,
'enclosures' => array($img),
'timestamp' => $tralbum_data->release_date
);
$item['categories'] = [];
foreach ($tralbum_data->tags as $tag) {
$item['categories'][] = $tag->norm_name;
}
$item['categories'] = array();
foreach ($tralbum_data->tags as $tag) {
$item['categories'][] = $tag->norm_name;
}
// Give articles a unique UID depending on its track list
// Releases should then show up as new articles when tracks are added
if ($this->getInput('type') === 'changes') {
$item['uid'] = "bandcamp/$band_data->band_id/$tralbum_data->id/";
foreach ($tralbum_data->tracks as $track) {
$item['uid'] .= $track->track_id;
}
}
// Give articles a unique UID depending on its track list
// Releases should then show up as new articles when tracks are added
if ($this->getInput('type') === 'changes') {
$item['uid'] = "bandcamp/$band_data->band_id/$tralbum_data->id/";
foreach ($tralbum_data->tracks as $track) {
$item['uid'] .= $track->track_id;
}
}
$item['content'] = "<img src='$small_img' /><br/>$full_title<br/>";
if ($tralbum_data->type === 'a') {
$item['content'] .= '<ol>';
foreach ($tralbum_data->tracks as $track) {
$item['content'] .= "<li>$track->title</li>";
}
$item['content'] .= '</ol>';
}
if (!empty($tralbum_data->about)) {
$item['content'] .= '<p>'
. nl2br($tralbum_data->about)
. '</p>';
}
$item['content'] = "<img src='$small_img' /><br/>$full_title<br/>";
if ($tralbum_data->type === 'a') {
$item['content'] .= '<ol>';
foreach ($tralbum_data->tracks as $track) {
$item['content'] .= "<li>$track->title</li>";
}
$item['content'] .= '</ol>';
}
if (!empty($tralbum_data->about)) {
$item['content'] .= '<p>'
. nl2br($tralbum_data->about)
. '</p>';
}
return $item;
}
return $item;
}
private function buildRequestJson()
{
$requestJson = [
'tag' => $this->getInput('tag'),
'page' => 1,
'sort' => 'date'
];
return json_encode($requestJson);
}
private function buildRequestJson(){
$requestJson = array(
'tag' => $this->getInput('tag'),
'page' => 1,
'sort' => 'date'
);
return json_encode($requestJson);
}
private function getImageUrl($id, $size)
{
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
}
private function getImageUrl($id, $size){
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
}
private function apiGet($endpoint, $query_data)
{
$url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data);
// todo: 429 Too Many Requests happens a lot
$data = json_decode(getContents($url));
return $data;
}
private function apiGet($endpoint, $query_data) {
$url = self::URI . 'api/' . $endpoint . '?' . http_build_query($query_data);
$data = json_decode(getContents($url));
return $data;
}
public function getURI()
{
switch ($this->queriedContext) {
case 'By tag':
if (!is_null($this->getInput('tag'))) {
return self::URI
. 'tag/'
. urlencode($this->getInput('tag'))
. '?sort_field=date';
}
break;
case 'By label':
if (!is_null($this->getInput('label'))) {
return 'https://'
. $this->getInput('label')
. '.bandcamp.com/music';
}
break;
case 'By band':
if (!is_null($this->getInput('band'))) {
return 'https://'
. $this->getInput('band')
. '.bandcamp.com/music';
}
break;
case 'By album':
if (!is_null($this->getInput('band')) && !is_null($this->getInput('album'))) {
return 'https://'
. $this->getInput('band')
. '.bandcamp.com/album/'
. $this->getInput('album');
}
break;
}
public function getURI(){
switch($this->queriedContext) {
case 'By tag':
if(!is_null($this->getInput('tag'))) {
return self::URI
. 'tag/'
. urlencode($this->getInput('tag'))
. '?sort_field=date';
}
break;
case 'By label':
if(!is_null($this->getInput('label'))) {
return 'https://'
. $this->getInput('label')
. '.bandcamp.com/music';
}
break;
case 'By band':
if(!is_null($this->getInput('band'))) {
return 'https://'
. $this->getInput('band')
. '.bandcamp.com/music';
}
break;
case 'By album':
if(!is_null($this->getInput('band')) && !is_null($this->getInput('album'))) {
return 'https://'
. $this->getInput('band')
. '.bandcamp.com/album/'
. $this->getInput('album');
}
break;
}
return parent::getURI();
}
return parent::getURI();
}
public function getName()
{
switch ($this->queriedContext) {
case 'By tag':
if (!is_null($this->getInput('tag'))) {
return $this->getInput('tag') . ' - Bandcamp Tag';
}
break;
case 'By band':
if (isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Band';
} elseif (!is_null($this->getInput('band'))) {
return $this->getInput('band') . ' - Bandcamp Band';
}
break;
case 'By label':
if (isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Label';
} elseif (!is_null($this->getInput('label'))) {
return $this->getInput('label') . ' - Bandcamp Label';
}
break;
case 'By album':
if (isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Album';
} elseif (!is_null($this->getInput('album'))) {
return $this->getInput('album') . ' - Bandcamp Album';
}
break;
}
public function getName(){
switch($this->queriedContext) {
case 'By tag':
if(!is_null($this->getInput('tag'))) {
return $this->getInput('tag') . ' - Bandcamp Tag';
}
break;
case 'By band':
if(isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Band';
} elseif(!is_null($this->getInput('band'))) {
return $this->getInput('band') . ' - Bandcamp Band';
}
break;
case 'By label':
if(isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Label';
} elseif(!is_null($this->getInput('label'))) {
return $this->getInput('label') . ' - Bandcamp Label';
}
break;
case 'By album':
if(isset($this->feedName)) {
return $this->feedName . ' - Bandcamp Album';
} elseif(!is_null($this->getInput('album'))) {
return $this->getInput('album') . ' - Bandcamp Album';
}
break;
}
return parent::getName();
}
return parent::getName();
}
public function detectParameters($url)
{
$params = [];
public function detectParameters($url) {
$params = array();
// By tag
$regex = '/^(https?:\/\/)?bandcamp\.com\/tag\/([^\/.&?\n]+)/';
if (preg_match($regex, $url, $matches) > 0) {
$params['tag'] = urldecode($matches[2]);
return $params;
}
// By tag
$regex = '/^(https?:\/\/)?bandcamp\.com\/tag\/([^\/.&?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['tag'] = urldecode($matches[2]);
return $params;
}
// By band
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/';
if (preg_match($regex, $url, $matches) > 0) {
$params['band'] = urldecode($matches[2]);
return $params;
}
// By band
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/';
if(preg_match($regex, $url, $matches) > 0) {
$params['band'] = urldecode($matches[2]);
return $params;
}
// By album
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/';
if (preg_match($regex, $url, $matches) > 0) {
$params['band'] = urldecode($matches[2]);
$params['album'] = urldecode($matches[3]);
return $params;
}
// By album
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['band'] = urldecode($matches[2]);
$params['album'] = urldecode($matches[3]);
return $params;
}
return null;
}
return null;
}
}

View File

@@ -1,164 +1,155 @@
<?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(
'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(
'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(
'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',
),
)
);
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 = [
'Latest articles' => [],
'Best of' => [
'best-content' => [
'name' => 'content',
'type' => 'list',
'values' => [
'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' => [
'genres-content' => [
'name' => 'content',
'type' => 'list',
'values' => [
'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' => [
'franchises-content' => [
'name' => 'content',
'type' => 'list',
'values' => [
'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
const CACHE_TIMEOUT = 3600; // 1 hour
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request: ' . $this->getURI());
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request: ' . $this->getURI());
$html = defaultLinkTo($html, self::URI);
$html = defaultLinkTo($html, self::URI);
$articles = $html->find('articles-list', 0);
$articles = $html->find('articles-list', 0);
foreach($articles->find('div.list-article') as $index => $article) {
$item = array();
foreach ($articles->find('div.list-article') as $index => $article) {
$item = [];
$articlePath = $article->find('a.title', 0)->href;
$articlePath = $article->find('a.title', 0)->href;
$articlePageHtml = getSimpleHTMLDOMCached($articlePath, 3600)
or returnServerError('Could not request: ' . $articlePath);
$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;
$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;
}
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;
$item['enclosures'][] = $articlePageHtml->find('meta[name="twitter:image"]', 0)->content;
$this->items[] = $item;
$this->items[] = $item;
if (count($this->items) >= 10) {
break;
}
}
}
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':
return self::URI . '/' . $this->getInput('content');
default:
return parent::getURI();
}
}
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() {
if ($this->queriedContext === 'Latest articles') {
return $this->queriedContext . ' - Bandcamp Daily';
}
public function getName()
{
switch ($this->queriedContext) {
case 'Latest articles':
return $this->queriedContext . ' - Bandcamp Daily';
case 'Best of':
case 'Genres':
case 'Franchises':
$contentKey = array_key_first(self::PARAMETERS[$this->queriedContext]);
$contentValues = array_flip(self::PARAMETERS[$this->queriedContext][$contentKey]['values']);
if (!is_null($this->getInput('content'))) {
$contentValues = array_flip(self::PARAMETERS[$this->queriedContext]['content']['values']);
return $contentValues[$this->getInput($contentKey)] . ' - Bandcamp Daily';
default:
return parent::getName();
}
}
return $contentValues[$this->getInput('content')] . ' - Bandcamp Daily';
}
return parent::getName();
}
}

View File

@@ -1,33 +1,31 @@
<?php
class BastaBridge extends BridgeAbstract {
class BastaBridge extends BridgeAbstract
{
const MAINTAINER = 'qwertygc';
const NAME = 'Bastamag Bridge';
const URI = 'https://www.bastamag.net/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns the newest articles.';
const MAINTAINER = 'qwertygc';
const NAME = 'Bastamag Bridge';
const URI = 'https://www.bastamag.net/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Returns the newest articles.';
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend');
public function collectData(){
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend');
$limit = 0;
$limit = 0;
foreach ($html->find('item') as $element) {
if ($limit < 10) {
$item = [];
$item['title'] = $element->find('title', 0)->innertext;
$item['uri'] = $element->find('guid', 0)->plaintext;
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
foreach($html->find('item') as $element) {
if($limit < 10) {
$item = array();
$item['title'] = $element->find('title', 0)->innertext;
$item['uri'] = $element->find('guid', 0)->plaintext;
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
$html = getSimpleHTMLDOM($item['uri']);
$html = defaultLinkTo($html, self::URI);
$html = getSimpleHTMLDOM($item['uri']);
$html = defaultLinkTo($html, self::URI);
$item['content'] = $html->find('div.texte', 0)->innertext;
$this->items[] = $item;
$limit++;
}
}
}
$item['content'] = $html->find('div.texte', 0)->innertext;
$this->items[] = $item;
$limit++;
}
}
}
}

View File

@@ -1,55 +1,97 @@
<?php
class BinanceBridge extends BridgeAbstract {
const NAME = 'Binance';
const URI = 'https://www.binance.com';
const DESCRIPTION = 'Subscribe to the Binance blog or the Binance Zendesk announcements.';
const MAINTAINER = 'thefranke';
const CACHE_TIMEOUT = 3600; // 1h
class BinanceBridge extends BridgeAbstract
{
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 getIcon() {
return 'https://bin.bnbstatic.com/static/images/common/favicon.ico';
}
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not fetch Binance blog data.');
public function getName() {
return self::NAME . ' ' . $this->getInput('category');
}
$appData = $html->find('script[id="__APP_DATA"]');
$appDataJson = json_decode($appData[0]->innertext);
$allposts = $appDataJson->routeProps->f3ac->blogListRes->list;
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';
}
foreach ($allposts as $element) {
$date = $element->releasedTime;
$title = $element->title;
$category = $element->category->name;
protected function collectBlogData() {
$html = getSimpleHTMLDOM($this->getURI());
$suburl = strtolower($category);
$suburl = str_replace(' ', '_', $suburl);
$appData = $html->find('script[id="__APP_DATA"]');
$appDataJson = json_decode($appData[0]->innertext);
$uri = self::URI . '/' . $suburl . '/' . $element->idStr;
foreach($appDataJson->pageData->redux->blogList->blogList as $element) {
$contentHTML = getSimpleHTMLDOMCached($uri);
$contentAppData = $contentHTML->find('script[id="__APP_DATA"]');
$contentAppDataJson = json_decode($contentAppData[0]->innertext);
$content = $contentAppDataJson->routeProps->a106->blogDetail->content;
$date = $element->postTime;
$abstract = $element->brief;
$uri = self::URI . '/' . $element->lang . '/blog/' . $element->idStr;
$title = $element->title;
$content = $element->content;
$item = [];
$item['title'] = $title;
$item['uri'] = $uri;
$item['timestamp'] = substr($date, 0, -3);
$item['author'] = 'Binance';
$item['content'] = $content;
$item['categories'] = $category;
$item = array();
$item['title'] = $title;
$item['uri'] = $uri;
$item['timestamp'] = substr($date, 0, -3);
$item['author'] = 'Binance';
$item['content'] = $content;
$this->items[] = $item;
$this->items[] = $item;
if (count($this->items) >= 10) {
break;
}
}
}
if (count($this->items) >= 10)
break;
}
}
protected function collectAnnouncementData() {
$html = getSimpleHTMLDOM($this->getURI());
foreach($html->find('a.article-list-link') as $a) {
$title = $a->innertext;
$uri = 'https://binance.zendesk.com' . $a->href;
$full = getSimpleHTMLDOMCached($uri);
$content = $full->find('div.article-body', 0);
$date = $full->find('time', 0)->getAttribute('datetime');
$item = array();
$item['title'] = $title;
$item['uri'] = $uri;
$item['timestamp'] = strtotime($date);
$item['author'] = 'Binance';
$item['content'] = $content;
$this->items[] = $item;
if (count($this->items) >= 10)
break;
}
}
public function collectData() {
if ($this->getInput('category') == 'Blog')
$this->collectBlogData();
else
$this->collectAnnouncementData();
}
}

View File

@@ -1,44 +1,45 @@
<?php
class BlaguesDeMerdeBridge extends BridgeAbstract {
class BlaguesDeMerdeBridge extends BridgeAbstract
{
const MAINTAINER = 'superbaillot.net, logmanoriginal';
const NAME = 'Blagues De Merde';
const URI = 'http://www.blaguesdemerde.fr/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Blagues De Merde';
const MAINTAINER = 'superbaillot.net, logmanoriginal';
const NAME = 'Blagues De Merde';
const URI = 'http://www.blaguesdemerde.fr/';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'Blagues De Merde';
public function getIcon()
{
return self::URI . 'assets/img/favicon.ico';
}
public function getIcon() {
return self::URI . 'assets/img/favicon.ico';
}
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI);
public function collectData(){
foreach ($html->find('div.blague') as $element) {
$item = [];
$html = getSimpleHTMLDOM(self::URI);
$item['uri'] = static::URI . '#' . $element->id;
$item['author'] = $element->find('div[class="blague-footer"] p strong', 0)->plaintext;
foreach($html->find('div.blague') as $element) {
// Let the title be everything up to the first <br>
$item['title'] = trim(explode("\n", $element->find('div.text', 0)->plaintext)[0]);
$item = array();
$item['content'] = strip_tags($element->find('div.text', 0));
$item['uri'] = static::URI . '#' . $element->id;
$item['author'] = $element->find('div[class="blague-footer"] p strong', 0)->plaintext;
// timestamp is part of:
// <p>Par <strong>{author}</strong> le {date} dans <strong>{category}</strong></p>
preg_match(
'/.+le(.+)dans.*/',
$element->find('div[class="blague-footer"]', 0)->plaintext,
$matches
);
// Let the title be everything up to the first <br>
$item['title'] = trim(explode("\n", $element->find('div.text', 0)->plaintext)[0]);
$item['timestamp'] = strtotime($matches[1]);
$item['content'] = strip_tags($element->find('div.text', 0));
$this->items[] = $item;
}
}
// timestamp is part of:
// <p>Par <strong>{author}</strong> le {date} dans <strong>{category}</strong></p>
preg_match(
'/.+le(.+)dans.*/',
$element->find('div[class="blague-footer"]', 0)->plaintext,
$matches
);
$item['timestamp'] = strtotime($matches[1]);
$this->items[] = $item;
}
}
}

View File

@@ -1,32 +1,29 @@
<?php
class BleepingComputerBridge extends FeedExpander {
class BleepingComputerBridge extends FeedExpander
{
const MAINTAINER = 'csisoap';
const NAME = 'Bleeping Computer';
const URI = 'https://www.bleepingcomputer.com/';
const DESCRIPTION = 'Returns the newest articles.';
const MAINTAINER = 'csisoap';
const NAME = 'Bleeping Computer';
const URI = 'https://www.bleepingcomputer.com/';
const DESCRIPTION = 'Returns the newest articles.';
protected function parseItem($item)
{
$item = parent::parseItem($item);
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 = getSimpleHTMLDOMCached($item['uri']);
if(!$article_html) {
$item['content'] .= '<p><em>Could not request ' . $this->getName() . ': ' . $item['uri'] . '</em></p>';
return $item;
}
$article_content = $article_html->find('div.articleBody', 0)->innertext;
$article_content = stripRecursiveHTMLSection($article_content, 'div', '<div class="cz-related-article-wrapp');
$item['content'] = trim($article_content);
$article_content = $article_html->find('div.articleBody', 0)->innertext;
$article_content = stripRecursiveHTMLSection($article_content, 'div', '<div class="cz-related-article-wrapp');
$item['content'] = trim($article_content);
return $item;
}
return $item;
}
public function collectData()
{
$feed = static::URI . 'feed/';
$this->collectExpandableDatas($feed);
}
public function collectData(){
$feed = static::URI . 'feed/';
$this->collectExpandableDatas($feed);
}
}

View File

@@ -1,60 +1,60 @@
<?php
class BlizzardNewsBridge extends XPathAbstract
{
const NAME = 'Blizzard News';
const URI = 'https://news.blizzard.com';
const DESCRIPTION = 'Blizzard (game company) newsfeed';
const MAINTAINER = 'Niehztog';
const PARAMETERS = [
'' => [
'locale' => [
'name' => 'Language',
'type' => 'list',
'values' => [
'Deutsch' => 'de-de',
'English (EU)' => 'en-gb',
'English (US)' => 'en-us',
'Español (EU)' => 'es-es',
'Español (AL)' => 'es-mx',
'Français' => 'fr-fr',
'Italiano' => 'it-it',
'日本語' => 'ja-jp',
'한국어' => 'ko-kr',
'Polski' => 'pl-pl',
'Português (AL)' => 'pt-br',
'Русский' => 'ru-ru',
'ภาษาไทย' => 'th-th',
'简体中文' => 'zh-cn',
'繁體中文' => 'zh-tw'
],
'defaultValue' => 'en-us',
'title' => 'Select your language'
]
]
];
const CACHE_TIMEOUT = 3600;
class BlizzardNewsBridge extends XPathAbstract {
const XPATH_EXPRESSION_ITEM = '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article';
const XPATH_EXPRESSION_ITEM_TITLE = './/div/div[2]/h2';
const XPATH_EXPRESSION_ITEM_CONTENT = './/div[@class="ArticleListItem-description"]/div[@class="h6"]';
const XPATH_EXPRESSION_ITEM_URI = './/a[@class="ArticleLink ArticleLink"]/@href';
const XPATH_EXPRESSION_ITEM_AUTHOR = '';
const XPATH_EXPRESSION_ITEM_TIMESTAMP = './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp';
const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/div[@class="ArticleListItem-image"]/@style';
const XPATH_EXPRESSION_ITEM_CATEGORIES = './/div[@class="ArticleListItem-label"]';
const SETTING_FIX_ENCODING = true;
const NAME = 'Blizzard News';
const URI = 'https://news.blizzard.com';
const DESCRIPTION = 'Blizzard (game company) newsfeed';
const MAINTAINER = 'Niehztog';
const PARAMETERS = array(
'' => array(
'locale' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'Deutsch' => 'de-de',
'English (EU)' => 'en-gb',
'English (US)' => 'en-us',
'Español (EU)' => 'es-es',
'Español (AL)' => 'es-mx',
'Français' => 'fr-fr',
'Italiano' => 'it-it',
'日本語' => 'ja-jp',
'한국어' => 'ko-kr',
'Polski' => 'pl-pl',
'Português (AL)' => 'pt-br',
'Русский' => 'ru-ru',
'ภาษาไทย' => 'th-th',
'简体中文' => 'zh-cn',
'繁體中文' => 'zh-tw'
),
'defaultValue' => 'en-us',
'title' => 'Select your language'
)
)
);
const CACHE_TIMEOUT = 3600;
/**
* Source Web page URL (should provide either HTML or XML content)
* @return string
*/
protected function getSourceUrl()
{
$locale = $this->getInput('locale');
if ('zh-cn' === $locale) {
return 'https://cn.news.blizzard.com';
}
return 'https://news.blizzard.com/' . $locale;
}
const XPATH_EXPRESSION_ITEM = '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article';
const XPATH_EXPRESSION_ITEM_TITLE = './/div/div[2]/h2';
const XPATH_EXPRESSION_ITEM_CONTENT = './/div[@class="ArticleListItem-description"]/div[@class="h6"]';
const XPATH_EXPRESSION_ITEM_URI = './/a[@class="ArticleLink ArticleLink"]/@href';
const XPATH_EXPRESSION_ITEM_AUTHOR = '';
const XPATH_EXPRESSION_ITEM_TIMESTAMP = './/time[@class="ArticleListItem-footerTimestamp"]/@timestamp';
const XPATH_EXPRESSION_ITEM_ENCLOSURES = './/div[@class="ArticleListItem-image"]/@style';
const XPATH_EXPRESSION_ITEM_CATEGORIES = './/div[@class="ArticleListItem-label"]';
const SETTING_FIX_ENCODING = true;
/**
* Source Web page URL (should provide either HTML or XML content)
* @return string
*/
protected function getSourceUrl(){
$locale = $this->getInput('locale');
if('zh-cn' === $locale) {
return 'https://cn.news.blizzard.com';
}
return 'https://news.blizzard.com/' . $locale;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,77 +1,49 @@
<?php
require_once('GelbooruBridge.php');
class BooruprojectBridge extends DanbooruBridge
{
const MAINTAINER = 'mitsukarenai';
const NAME = 'Booruproject';
const URI = 'https://booru.org/';
const DESCRIPTION = 'Returns images from given page of booruproject';
const PARAMETERS = [
'global' => [
'p' => [
'name' => 'page',
'defaultValue' => 0,
'type' => 'number'
],
't' => [
'name' => 'tags',
'required' => true,
'exampleValue' => 'tagme',
'title' => 'Use "all" to get all posts'
]
],
'Booru subdomain (subdomain.booru.org)' => [
'i' => [
'name' => 'Subdomain',
'required' => true,
'exampleValue' => 'rm'
]
]
];
class BooruprojectBridge extends GelbooruBridge {
const PATHTODATA = '.thumb';
const IDATTRIBUTE = 'id';
const TAGATTRIBUTE = 'title';
const PIDBYPAGE = 20;
const MAINTAINER = 'mitsukarenai';
const NAME = 'Booruproject';
const URI = 'https://booru.org/';
const DESCRIPTION = 'Returns images from given page of booruproject';
const PARAMETERS = array(
'global' => array(
'p' => array(
'name' => 'page',
'type' => 'number'
),
't' => array(
'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,
'exampleValue' => 'rm'
)
)
);
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'));
}
const PIDBYPAGE = 20;
protected function getTags($element)
{
$tags = parent::getTags($element);
$tags = explode(' ', $tags);
public function getURI(){
if(!is_null($this->getInput('i'))) {
return 'https://' . $this->getInput('i') . '.booru.org/';
}
// Remove statistics from the tags list (identified by colon)
foreach ($tags as $key => $tag) {
if (strpos($tag, ':') !== false) {
unset($tags[$key]);
}
}
return parent::getURI();
}
return implode(' ', $tags);
}
public function getName(){
if(!is_null($this->getInput('i'))) {
return static::NAME . ' ' . $this->getInput('i');
}
public function getURI()
{
if (!is_null($this->getInput('i'))) {
return 'https://' . $this->getInput('i') . '.booru.org/';
}
return parent::getURI();
}
public function getName()
{
if (!is_null($this->getInput('i'))) {
return static::NAME . ' ' . $this->getInput('i');
}
return parent::getName();
}
return parent::getName();
}
}

View File

@@ -1,124 +1,157 @@
<?php
class BrutBridge extends BridgeAbstract {
const NAME = 'Brut Bridge';
const URI = 'https://www.brut.media';
const DESCRIPTION = 'Returns 5 newest videos by category and edition';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(array(
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'News' => 'news',
'International' => 'international',
'Economy' => 'economy',
'Science and Technology' => 'science-and-technology',
'Entertainment' => 'entertainment',
'Sports' => 'sport',
'Nature' => 'nature',
'Health' => 'health',
),
'defaultValue' => 'news',
),
'edition' => array(
'name' => ' Edition',
'type' => 'list',
'values' => array(
'United States' => 'us',
'United Kingdom' => 'uk',
'France' => 'fr',
'Spain' => 'es',
'India' => 'in',
'Mexico' => 'mx',
),
'defaultValue' => 'us',
)
)
);
class BrutBridge extends BridgeAbstract
{
const NAME = 'Brut Bridge';
const URI = 'https://www.brut.media';
const DESCRIPTION = 'Returns 10 newest videos by category and edition';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = [[
'category' => [
'name' => 'Category',
'type' => 'list',
'values' => [
'News' => 'news',
'International' => 'international',
'Economy' => 'economy',
'Science and Technology' => 'science-and-technology',
'Entertainment' => 'entertainment',
'Sports' => 'sport',
'Nature' => 'nature',
'Health' => 'health',
],
'defaultValue' => 'news',
],
'edition' => [
'name' => ' Edition',
'type' => 'list',
'values' => [
'United States' => 'us',
'United Kingdom' => 'uk',
'France' => 'fr',
'Spain' => 'es',
'India' => 'in',
'Mexico' => 'mx',
],
'defaultValue' => 'us',
]
]
];
const CACHE_TIMEOUT = 1800; // 30 mins
const CACHE_TIMEOUT = 1800; // 30 mins
private $videoId = '';
private $videoType = '';
private $videoImage = '';
private $jsonRegex = '/window\.__PRELOADED_STATE__ = ((?:.*)});/';
public function collectData() {
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI());
$html = getSimpleHTMLDOM($this->getURI());
$results = $html->find('div.results', 0);
$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 $li) {
$item = [];
foreach($results->find('li.col-6.col-sm-4.col-md-3.col-lg-2.px-2.pb-4') as $index => $li) {
$item = array();
$videoPath = self::URI . $li->children(0)->href;
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600);
$videoPath = self::URI . $li->children(0)->href;
$json = $this->extractJson($videoPageHtml);
$id = array_keys((array) $json->media->index)[0];
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600);
$item['uri'] = $videoPath;
$item['title'] = $json->media->index->$id->title;
$item['timestamp'] = $json->media->index->$id->published_at;
$item['enclosures'][] = $json->media->index->$id->media->thumbnail;
$this->videoImage = $videoPageHtml->find('meta[name="twitter:image"]', 0)->content;
$description = $json->media->index->$id->description;
$article = '';
$this->processTwitterImage();
if (is_null($json->media->index->$id->media->seo_article) === false) {
$article = markdownToHtml($json->media->index->$id->media->seo_article);
}
$description = $videoPageHtml->find('div.description', 0);
$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;
$item['uri'] = $videoPath;
$item['title'] = $description->find('h1', 0)->plaintext;
$this->items[] = $item;
if ($description->find('div.date', 0)->children(0)) {
$description->find('div.date', 0)->children(0)->outertext = '';
}
if (count($this->items) >= 10) {
break;
}
}
}
$item['content'] = $this->processContent(
$description
);
public function getURI()
{
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
return self::URI . '/' . $this->getInput('edition') . '/' . $this->getInput('category');
}
$item['timestamp'] = $this->processDate($description);
$item['enclosures'][] = $this->videoImage;
return parent::getURI();
}
$this->items[] = $item;
public function getName()
{
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
return $this->getKey('category') . ' - ' .
$this->getKey('edition') . ' - Brut.';
}
if (count($this->items) >= 5) {
break;
}
}
}
return parent::getName();
}
public function getURI() {
/**
* Extract JSON from page
*/
private function extractJson($html)
{
if (!preg_match($this->jsonRegex, $html, $parts)) {
returnServerError('Failed to extract data from page');
}
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
return self::URI . '/' . $this->getInput('edition') . '/' . $this->getInput('category');
}
$data = json_decode($parts[1]);
return parent::getURI();
}
if ($data === false) {
returnServerError('Failed to decode extracted data');
}
public function getName() {
return $data;
}
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
$parameters = $this->getParameters();
$editionValues = array_flip($parameters[0]['edition']['values']);
$categoryValues = array_flip($parameters[0]['category']['values']);
return $categoryValues[$this->getInput('category')] . ' - ' .
$editionValues[$this->getInput('edition')] . ' - Brut.';
}
return parent::getName();
}
private function processDate($description) {
if ($this->getInput('edition') === 'uk') {
$date = DateTime::createFromFormat('d/m/Y H:i', $description->find('div.date', 0)->innertext);
return strtotime($date->format('Y-m-d H:i:s'));
}
return strtotime($description->find('div.date', 0)->innertext);
}
private function processContent($description) {
$content = '<video controls poster="' . $this->videoImage . '" preload="none">
<source src="https://content.brut.media/video/' . $this->videoId . '-' . $this->videoType . '-web.mp4"
type="video/mp4">
</video>';
$content .= '<p>' . $description->find('h2.mb-1', 0)->innertext . '</p>';
if ($description->find('div.text.pb-3', 0)->children(1)->class != 'date') {
$content .= '<p>' . $description->find('div.text.pb-3', 0)->children(1)->innertext . '</p>';
}
return $content;
}
private function processTwitterImage() {
/**
* Extract video ID + type from twitter image
*
* Example (wrapped):
* https://img.brut.media/thumbnail/
* the-life-of-rita-moreno-2cce75b5-d448-44d2-a97c-ca50d6470dd4-square.jpg
* ?ts=1559337892
*/
$fpath = parse_url($this->videoImage, PHP_URL_PATH);
$fname = basename($fpath);
$fname = substr($fname, 0, strrpos($fname, '.'));
$parts = explode('-', $fname);
if (end($parts) === 'auto') {
$key = array_search('auto', $parts);
unset($parts[$key]);
}
$this->videoId = implode('-', array_splice($parts, -6, 5));
$this->videoType = end($parts);
}
}

View File

@@ -1,198 +0,0 @@
<?php
class BugzillaBridge extends BridgeAbstract
{
const NAME = 'Bugzilla Bridge';
const URI = 'https://www.bugzilla.org/';
const DESCRIPTION = 'Bridge for any Bugzilla instance';
const MAINTAINER = 'Yaman Qalieh';
const PARAMETERS = [
'global' => [
'instance' => [
'name' => 'Instance URL',
'required' => true,
'exampleValue' => 'https://bugzilla.mozilla.org'
]
],
'Bug comments' => [
'id' => [
'name' => 'Bug tracking ID',
'type' => 'number',
'required' => true,
'title' => 'Insert bug tracking ID',
'exampleValue' => 121241
],
'limit' => [
'name' => 'Number of comments to return',
'type' => 'number',
'required' => false,
'title' => 'Specify number of comments to return',
'defaultValue' => -1
],
'skiptags' => [
'name' => 'Skip offtopic comments',
'type' => 'checkbox',
'title' => 'Excludes comments tagged as advocacy, metoo, or offtopic from the feed'
]
]
];
const SKIPPED_ACTIVITY = [
'cc' => true,
'comment_tag' => true
];
const SKIPPED_TAGS = ['advocacy', 'metoo', 'offtopic'];
private $instance;
private $bugid;
private $buguri;
private $title;
public function getName()
{
if (!is_null($this->title)) {
return $this->title;
}
return parent::getName();
}
public function getURI()
{
return $this->buguri ?? parent::getURI();
}
public function collectData()
{
$this->instance = rtrim($this->getInput('instance'), '/');
$this->bugid = $this->getInput('id');
$this->buguri = $this->instance . '/show_bug.cgi?id=' . $this->bugid;
$url = $this->instance . '/rest/bug/' . $this->bugid;
$this->getTitle($url);
$this->collectComments($url . '/comment');
$this->collectUpdates($url . '/history');
usort($this->items, function ($a, $b) {
return $b['timestamp'] <=> $a['timestamp'];
});
if ($this->getInput('limit') > 0) {
$this->items = array_slice($this->items, 0, $this->getInput('limit'));
}
}
protected function getTitle($url)
{
// Only request the summary for a faster request
$json = self::getJSON($url . '?include_fields=summary');
$this->title = 'Bug ' . $this->bugid . ' - ' .
$json['bugs'][0]['summary'] . ' - ' .
// Remove https://
substr($this->instance, 8);
}
protected function collectComments($url)
{
$json = self::getJSON($url);
// Array of comments is here
if (!isset($json['bugs'][$this->bugid]['comments'])) {
returnClientError('Cannot find REST endpoint');
}
foreach ($json['bugs'][$this->bugid]['comments'] as $comment) {
$item = [];
if (
$this->getInput('skiptags') and
array_intersect(self::SKIPPED_TAGS, $comment['tags'])
) {
continue;
}
$item['categories'] = $comment['tags'];
$item['uri'] = $this->buguri . '#c' . $comment['count'];
$item['title'] = 'Comment ' . $comment['count'];
$item['timestamp'] = $comment['creation_time'];
$item['author'] = $this->getUser($comment['creator']);
$item['content'] = $comment['text'];
if (isset($comment['is_markdown']) and $comment['is_markdown']) {
$item['content'] = markdownToHtml($item['content']);
}
if (!is_null($comment['attachment_id'])) {
$item['enclosures'] = [$this->instance . '/attachment.cgi?id=' . $comment['attachment_id']];
}
$this->items[] = $item;
}
}
protected function collectUpdates($url)
{
$json = self::getJSON($url);
// Array of changesets which contain an array of changes
if (!isset($json['bugs']['0']['history'])) {
returnClientError('Cannot find REST endpoint');
}
foreach ($json['bugs']['0']['history'] as $changeset) {
$author = $this->getUser($changeset['who']);
$timestamp = $changeset['when'];
foreach ($changeset['changes'] as $change) {
// Skip updates to the cc list and comment tagging
if (isset(self::SKIPPED_ACTIVITY[$change['field_name']])) {
continue;
}
$item = [];
$item['uri'] = $this->buguri;
$item['title'] = 'Updated';
$item['timestamp'] = $timestamp;
$item['author'] = $author;
$item['content'] = ucfirst($change['field_name']) . ': ' .
($change['removed'] === '' ? '[nothing]' : $change['removed']) . ' -> ' .
($change['added'] === '' ? '[nothing]' : $change['added']);
$this->items[] = $item;
}
}
}
protected function getUser($user)
{
// Check if the user endpoint is available
if ($this->loadCacheValue($this->instance . 'userEndpointClosed', 86400)) {
return $user;
}
$cache = $this->loadCacheValue($this->instance . $user);
if (!is_null($cache)) {
return $cache;
}
$url = $this->instance . '/rest/user/' . $user . '?include_fields=real_name';
try {
$json = self::getJSON($url);
if (isset($json['error']) and $json['error']) {
throw new Exception();
}
} catch (Exception $e) {
$this->saveCacheValue($this->instance . 'userEndpointClosed', true);
return $user;
}
$username = $json['users']['0']['real_name'];
if (empty($username)) {
$username = $user;
}
$this->saveCacheValue($this->instance . $user, $username);
return $username;
}
protected static function getJSON($url)
{
$headers = [
'Accept: application/json',
];
return json_decode(getContents($url, $headers), true);
}
}

View File

@@ -2,219 +2,217 @@
class BukowskisBridge extends BridgeAbstract
{
const NAME = 'Bukowskis';
const URI = 'https://www.bukowskis.com';
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
const MAINTAINER = 'Qluxzz';
const PARAMETERS = [[
'category' => [
'name' => 'Category',
'type' => 'list',
'values' => [
'All categories' => '',
'Art' => [
'All' => 'art',
'Classic Art' => 'art.classic-art',
'Classic Finnish Art' => 'art.classic-finnish-art',
'Classic Swedish Art' => 'art.classic-swedish-art',
'Contemporary' => 'art.contemporary',
'Modern Finnish Art' => 'art.modern-finnish-art',
'Modern International Art' => 'art.modern-international-art',
'Modern Swedish Art' => 'art.modern-swedish-art',
'Old Masters' => 'art.old-masters',
'Other' => 'art.other',
'Photographs' => 'art.photographs',
'Prints' => 'art.prints',
'Sculpture' => 'art.sculpture',
'Swedish Old Masters' => 'art.swedish-old-masters',
],
'Asian Ceramics & Works of Art' => [
'All' => 'asian-ceramics-works-of-art',
'Other' => 'asian-ceramics-works-of-art.other',
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
],
'Books & Manuscripts' => [
'All' => 'books-manuscripts',
'Books' => 'books-manuscripts.books',
],
'Carpets, rugs & textiles' => [
'All' => 'carpets-rugs-textiles',
'European' => 'carpets-rugs-textiles.european',
'Oriental' => 'carpets-rugs-textiles.oriental',
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
],
'Ceramics & porcelain' => [
'All' => 'ceramics-porcelain',
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
'European' => 'ceramics-porcelain.european',
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
'Scandinavian' => 'ceramics-porcelain.scandinavian',
],
'Collectibles' => [
'All' => 'collectibles',
'Advertising & Retail' => 'collectibles.advertising-retail',
'Memorabilia' => 'collectibles.memorabilia',
'Movies & music' => 'collectibles.movies-music',
'Other' => 'collectibles.other',
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
'Technica & Nautica' => 'collectibles.technica-nautica',
'Toys' => 'collectibles.toys',
],
'Design' => [
'All' => 'design',
'Art glass' => 'design.art-glass',
'Furniture' => 'design.furniture',
'Other' => 'design.other',
],
'Folk art' => [
'All' => 'folk-art',
'All categories' => 'lots',
],
'Furniture' => [
'All' => 'furniture',
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
'Chairs' => 'furniture.chairs',
'Garden furniture' => 'furniture.garden-furniture',
'Mirrors' => 'furniture.mirrors',
'Other' => 'furniture.other',
'Shelves & Book cases' => 'furniture.shelves-book-cases',
'Tables' => 'furniture.tables',
],
'Glassware' => [
'All' => 'glassware',
'Glassware' => 'glassware.glassware',
'Other' => 'glassware.other',
],
'Jewellery' => [
'All' => 'jewellery',
'Bracelets' => 'jewellery.bracelets',
'Brooches' => 'jewellery.brooches',
'Earrings' => 'jewellery.earrings',
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
'Other' => 'jewellery.other',
'Rings' => 'jewellery.rings',
],
'Lighting' => [
'All' => 'lighting',
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
'Ceiling lights' => 'lighting.ceiling-lights',
'Chandeliers' => 'lighting.chandeliers',
'Floor lights' => 'lighting.floor-lights',
'Other' => 'lighting.other',
'Table lights' => 'lighting.table-lights',
'Wall lights' => 'lighting.wall-lights',
],
'Militaria' => [
'All' => 'militaria',
'Honors & Medals' => 'militaria.honors-medals',
'Other militaria' => 'militaria.other-militaria',
'Weaponry' => 'militaria.weaponry',
],
'Miscellaneous' => [
'All' => 'miscellaneous',
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
'Nickel silver' => 'miscellaneous.nickel-silver',
'Oriental' => 'miscellaneous.oriental',
'Other' => 'miscellaneous.other',
],
'Silver' => [
'All' => 'silver',
'Candle sticks' => 'silver.candle-sticks',
'Cups & Bowls' => 'silver.cups-bowls',
'Cutlery' => 'silver.cutlery',
'Other' => 'silver.other',
],
'Timepieces' => [
'All' => 'timepieces',
'Other' => 'timepieces.other',
'Pocket watches' => 'timepieces.pocket-watches',
'Table clocks' => 'timepieces.table-clocks',
'Wrist watches' => 'timepieces.wrist-watches',
],
'Vintage & Fashion' => [
'All' => 'vintage-fashion',
'Accessories' => 'vintage-fashion.accessories',
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
'Clothes' => 'vintage-fashion.clothes',
],
]
],
'sort_order' => [
'name' => 'Sort order',
'type' => 'list',
'values' => [
'Ending soon' => 'ending',
'Most recent' => 'recent',
'Most bids' => 'most',
'Fewest bids' => 'fewest',
'Lowest price' => 'lowest',
'Highest price' => 'highest',
'Lowest estimate' => 'low',
'Highest estimate' => 'high',
'Alphabetical' => 'alphabetical',
],
],
'language' => [
'name' => 'Language',
'type' => 'list',
'values' => [
'English' => 'en',
'Swedish' => 'sv',
'Finnish' => 'fi'
],
],
]];
const NAME = 'Bukowskis';
const URI = 'https://www.bukowskis.com';
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
const MAINTAINER = 'Qluxzz';
const PARAMETERS = array(array(
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'All categories' => '',
'Art' => array(
'All' => 'art',
'Classic Art' => 'art.classic-art',
'Classic Finnish Art' => 'art.classic-finnish-art',
'Classic Swedish Art' => 'art.classic-swedish-art',
'Contemporary' => 'art.contemporary',
'Modern Finnish Art' => 'art.modern-finnish-art',
'Modern International Art' => 'art.modern-international-art',
'Modern Swedish Art' => 'art.modern-swedish-art',
'Old Masters' => 'art.old-masters',
'Other' => 'art.other',
'Photographs' => 'art.photographs',
'Prints' => 'art.prints',
'Sculpture' => 'art.sculpture',
'Swedish Old Masters' => 'art.swedish-old-masters',
),
'Asian Ceramics & Works of Art' => array(
'All' => 'asian-ceramics-works-of-art',
'Other' => 'asian-ceramics-works-of-art.other',
'Porcelain' => 'asian-ceramics-works-of-art.porcelain',
),
'Books & Manuscripts' => array(
'All' => 'books-manuscripts',
'Books' => 'books-manuscripts.books',
),
'Carpets, rugs & textiles' => array(
'All' => 'carpets-rugs-textiles',
'European' => 'carpets-rugs-textiles.european',
'Oriental' => 'carpets-rugs-textiles.oriental',
'Rest of the world' => 'carpets-rugs-textiles.rest-of-the-world',
'Scandinavian' => 'carpets-rugs-textiles.scandinavian',
),
'Ceramics & porcelain' => array(
'All' => 'ceramics-porcelain',
'Ceramic ware' => 'ceramics-porcelain.ceramic-ware',
'European' => 'ceramics-porcelain.european',
'Rest of the world' => 'ceramics-porcelain.rest-of-the-world',
'Scandinavian' => 'ceramics-porcelain.scandinavian',
),
'Collectibles' => array(
'All' => 'collectibles',
'Advertising & Retail' => 'collectibles.advertising-retail',
'Memorabilia' => 'collectibles.memorabilia',
'Movies & music' => 'collectibles.movies-music',
'Other' => 'collectibles.other',
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
'Technica & Nautica' => 'collectibles.technica-nautica',
'Toys' => 'collectibles.toys',
),
'Design' => array(
'All' => 'design',
'Art glass' => 'design.art-glass',
'Furniture' => 'design.furniture',
'Other' => 'design.other',
),
'Folk art' => array(
'All' => 'folk-art',
'All categories' => 'lots',
),
'Furniture' => array(
'All' => 'furniture',
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
'Chairs' => 'furniture.chairs',
'Garden furniture' => 'furniture.garden-furniture',
'Mirrors' => 'furniture.mirrors',
'Other' => 'furniture.other',
'Shelves & Book cases' => 'furniture.shelves-book-cases',
'Tables' => 'furniture.tables',
),
'Glassware' => array(
'All' => 'glassware',
'Glassware' => 'glassware.glassware',
'Other' => 'glassware.other',
),
'Jewellery' => array(
'All' => 'jewellery',
'Bracelets' => 'jewellery.bracelets',
'Brooches' => 'jewellery.brooches',
'Earrings' => 'jewellery.earrings',
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
'Other' => 'jewellery.other',
'Rings' => 'jewellery.rings',
),
'Lighting' => array(
'All' => 'lighting',
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
'Ceiling lights' => 'lighting.ceiling-lights',
'Chandeliers' => 'lighting.chandeliers',
'Floor lights' => 'lighting.floor-lights',
'Other' => 'lighting.other',
'Table lights' => 'lighting.table-lights',
'Wall lights' => 'lighting.wall-lights',
),
'Militaria' => array(
'All' => 'militaria',
'Honors & Medals' => 'militaria.honors-medals',
'Other militaria' => 'militaria.other-militaria',
'Weaponry' => 'militaria.weaponry',
),
'Miscellaneous' => array(
'All' => 'miscellaneous',
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
'Nickel silver' => 'miscellaneous.nickel-silver',
'Oriental' => 'miscellaneous.oriental',
'Other' => 'miscellaneous.other',
),
'Silver' => array(
'All' => 'silver',
'Candle sticks' => 'silver.candle-sticks',
'Cups & Bowls' => 'silver.cups-bowls',
'Cutlery' => 'silver.cutlery',
'Other' => 'silver.other',
),
'Timepieces' => array(
'All' => 'timepieces',
'Other' => 'timepieces.other',
'Pocket watches' => 'timepieces.pocket-watches',
'Table clocks' => 'timepieces.table-clocks',
'Wrist watches' => 'timepieces.wrist-watches',
),
'Vintage & Fashion' => array(
'All' => 'vintage-fashion',
'Accessories' => 'vintage-fashion.accessories',
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
'Clothes' => 'vintage-fashion.clothes',
),
)
),
'sort_order' => array(
'name' => 'Sort order',
'type' => 'list',
'values' => array(
'Ending soon' => 'ending',
'Most recent' => 'recent',
'Most bids' => 'most',
'Fewest bids' => 'fewest',
'Lowest price' => 'lowest',
'Highest price' => 'highest',
'Lowest estimate' => 'low',
'Highest estimate' => 'high',
'Alphabetical' => 'alphabetical',
),
),
'language' => array(
'name' => 'Language',
'type' => 'list',
'values' => array(
'English' => 'en',
'Swedish' => 'sv',
'Finnish' => 'fi'
),
),
));
const CACHE_TIMEOUT = 3600; // 1 hour
const CACHE_TIMEOUT = 3600; // 1 hour
private $title;
private $title;
public function collectData()
{
$baseUrl = 'https://www.bukowskis.com';
$category = $this->getInput('category');
$language = $this->getInput('language');
$sort_order = $this->getInput('sort_order');
public function collectData()
{
$baseUrl = 'https://www.bukowskis.com';
$category = $this->getInput('category');
$language = $this->getInput('language');
$sort_order = $this->getInput('sort_order');
$url = $baseUrl . '/' . $language . '/lots';
$url = $baseUrl . '/' . $language . '/lots';
if ($category) {
$url = $url . '/category/' . $category;
}
if ($category)
$url = $url . '/category/' . $category;
if ($sort_order) {
$url = $url . '/sort/' . $sort_order;
}
if ($sort_order)
$url = $url . '/sort/' . $sort_order;
$html = getSimpleHTMLDOM($url);
$html = getSimpleHTMLDOM($url);
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
$this->title = htmlspecialchars_decode($html->find('title', 0)->innertext);
foreach ($html->find('div.c-lot-index-lot') as $lot) {
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
$images = json_decode(
htmlspecialchars_decode(
$lot
->find('img.o-aspect-ratio__image', 0)
->getAttribute('data-thumbnails')
)
);
foreach ($html->find('div.c-lot-index-lot') as $lot) {
$title = $lot->find('a.c-lot-index-lot__title', 0)->plaintext;
$relative_url = $lot->find('a.c-lot-index-lot__link', 0)->href;
$images = json_decode(
htmlspecialchars_decode(
$lot
->find('img.o-aspect-ratio__image', 0)
->getAttribute('data-thumbnails')
)
);
$this->items[] = [
'title' => $title,
'uri' => $baseUrl . $relative_url,
'uid' => $lot->getAttribute('data-lot-id'),
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
'enclosures' => array_slice($images, 1),
];
}
}
$this->items[] = array(
'title' => $title,
'uri' => $baseUrl . $relative_url,
'uid' => $lot->getAttribute('data-lot-id'),
'content' => count($images) > 0 ? "<img src='$images[0]'/><br/>$title" : $title,
'enclosures' => array_slice($images, 1),
);
}
}
public function getName()
{
return $this->title ?: parent::getName();
}
public function getName()
{
return $this->title ?: parent::getName();
}
}

View File

@@ -1,88 +1,84 @@
<?php
class BundesbankBridge extends BridgeAbstract {
class BundesbankBridge extends BridgeAbstract
{
const PARAM_LANG = 'lang';
const PARAM_LANG = 'lang';
const LANG_EN = 'en';
const LANG_DE = 'de';
const LANG_EN = 'en';
const LANG_DE = 'de';
const NAME = 'Bundesbank Bridge';
const URI = 'https://www.bundesbank.de/';
const DESCRIPTION = 'Returns the latest studies of the Bundesbank (Germany)';
const MAINTAINER = 'logmanoriginal';
const CACHE_TIMEOUT = 86400; // 24 hours
const NAME = 'Bundesbank Bridge';
const URI = 'https://www.bundesbank.de/';
const DESCRIPTION = 'Returns the latest studies of the Bundesbank (Germany)';
const MAINTAINER = 'logmanoriginal';
const CACHE_TIMEOUT = 86400; // 24 hours
const PARAMETERS = [
[
self::PARAM_LANG => [
'name' => 'Language',
'type' => 'list',
'defaultValue' => self::LANG_DE,
'values' => [
'English' => self::LANG_EN,
'Deutsch' => self::LANG_DE
]
]
]
];
const PARAMETERS = array(
array(
self::PARAM_LANG => array(
'name' => 'Language',
'type' => 'list',
'defaultValue' => self::LANG_DE,
'values' => array(
'English' => self::LANG_EN,
'Deutsch' => self::LANG_DE
)
)
)
);
public function getIcon()
{
return self::URI . 'resource/crblob/1890/a7f48ee0ae35348748121770ba3ca009/mL/favicon-ico-data.ico';
}
public function getIcon() {
return self::URI . 'resource/crblob/1890/a7f48ee0ae35348748121770ba3ca009/mL/favicon-ico-data.ico';
}
public function getURI()
{
switch ($this->getInput(self::PARAM_LANG)) {
case self::LANG_EN:
return self::URI . 'en/publications/reports/studies';
case self::LANG_DE:
return self::URI . 'de/publikationen/berichte/studien';
}
public function getURI() {
switch($this->getInput(self::PARAM_LANG)) {
case self::LANG_EN: return self::URI . 'en/publications/reports/studies';
case self::LANG_DE: return self::URI . 'de/publikationen/berichte/studien';
}
return parent::getURI();
}
return parent::getURI();
}
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI());
public function collectData() {
$html = defaultLinkTo($html, $this->getURI());
$html = getSimpleHTMLDOM($this->getURI());
foreach ($html->find('ul.resultlist li') as $study) {
$item = [];
$html = defaultLinkTo($html, $this->getURI());
$item['uri'] = $study->find('.teasable__link', 0)->href;
foreach($html->find('ul.resultlist li') as $study) {
$item = array();
// Get title without child elements (i.e. subtitle)
$title = $study->find('.teasable__title div.h2', 0);
$item['uri'] = $study->find('.teasable__link', 0)->href;
foreach ($title->children as &$child) {
$child->outertext = '';
}
// Get title without child elements (i.e. subtitle)
$title = $study->find('.teasable__title div.h2', 0);
$item['title'] = $title->innertext;
foreach($title->children as &$child) {
$child->outertext = '';
}
// Add subtitle to the content if it exists
$item['content'] = '';
$item['title'] = $title->innertext;
if ($subtitle = $study->find('.teasable__subtitle', 0)) {
$item['content'] .= '<strong>' . $study->find('.teasable__subtitle', 0)->plaintext . '</strong>';
}
// Add subtitle to the content if it exists
$item['content'] = '';
$item['content'] .= '<p>' . $study->find('.teasable__text', 0)->plaintext . '</p>';
if($subtitle = $study->find('.teasable__subtitle', 0)) {
$item['content'] .= '<strong>' . $study->find('.teasable__subtitle', 0)->plaintext . '</strong>';
}
$item['timestamp'] = strtotime($study->find('.teasable__date', 0)->plaintext);
$item['content'] .= '<p>' . $study->find('.teasable__text', 0)->plaintext . '</p>';
// Downloads and older studies don't have images
if ($study->find('.teasable__image', 0)) {
$item['enclosures'] = [
$study->find('.teasable__image img', 0)->src
];
}
$item['timestamp'] = strtotime($study->find('.teasable__date', 0)->plaintext);
$this->items[] = $item;
}
}
// Downloads and older studies don't have images
if($study->find('.teasable__image', 0)) {
$item['enclosures'] = array(
$study->find('.teasable__image img', 0)->src
);
}
$this->items[] = $item;
}
}
}

View File

@@ -1,94 +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';
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
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 getIcon()
{
return 'https://www.bundestag.de/static/appdata/includes/images/layout/favicon.ico';
}
public function collectData()
{
$ajaxUri = <<<URI
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.');
// 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.');
// 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;
$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);
// 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.');
$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;
}
}
}
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;
}
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 = [];
$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);
// | 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);
// Strip whitespace from date string.
$date = str_replace(' ', '', $date);
$content = sprintf(self::CONTENT_TEMPLATE, $party, $amount, $donor, $date);
$content = sprintf(self::CONTENT_TEMPLATE, $party, $amount, $donor, $date);
$item = [
'title' => $party . ': ' . $amount,
'content' => $content,
'uid' => sha1($content),
];
$item = array(
'title' => $party . ': ' . $amount,
'content' => $content,
'uid' => sha1($content),
);
// Try to get the link to the official document
if ($dip != null) {
$item['enclosures'] = [$dip->href];
}
// 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();
}
// Try to parse the date
$dateTime = DateTime::createFromFormat('d.m.Y', $date);
if ($dateTime !== false)
$item['timestamp'] = $dateTime->getTimestamp();
return $item;
}
return $item;
}
}

View File

@@ -1,38 +0,0 @@
<?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 = [];
$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;
}
}
}
}

View File

@@ -1,114 +1,108 @@
<?php
class CNETBridge extends BridgeAbstract {
class CNETBridge extends BridgeAbstract
{
const MAINTAINER = 'ORelio';
const NAME = 'CNET News';
const URI = 'https://www.cnet.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns the newest articles.';
const PARAMETERS = [
[
'topic' => [
'name' => 'Topic',
'type' => 'list',
'values' => [
'All articles' => '',
'Apple' => 'apple',
'Google' => 'google',
'Microsoft' => 'tags-microsoft',
'Computers' => 'topics-computers',
'Mobile' => 'topics-mobile',
'Sci-Tech' => 'topics-sci-tech',
'Security' => 'topics-security',
'Internet' => 'topics-internet',
'Tech Industry' => 'topics-tech-industry'
]
]
]
];
const MAINTAINER = 'ORelio';
const NAME = 'CNET News';
const URI = 'https://www.cnet.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns the newest articles.';
const PARAMETERS = array(
array(
'topic' => array(
'name' => 'Topic',
'type' => 'list',
'values' => array(
'All articles' => '',
'Apple' => 'apple',
'Google' => 'google',
'Microsoft' => 'tags-microsoft',
'Computers' => 'topics-computers',
'Mobile' => 'topics-mobile',
'Sci-Tech' => 'topics-sci-tech',
'Security' => 'topics-security',
'Internet' => 'topics-internet',
'Tech Industry' => 'topics-tech-industry'
)
)
)
);
private function cleanArticle($article_html)
{
$offset_p = strpos($article_html, '<p>');
$offset_figure = strpos($article_html, '<figure');
$offset = ($offset_figure < $offset_p ? $offset_figure : $offset_p);
$article_html = substr($article_html, $offset);
$article_html = str_replace('href="/', 'href="' . self::URI, $article_html);
$article_html = str_replace(' height="0"', '', $article_html);
$article_html = str_replace('<noscript>', '', $article_html);
$article_html = str_replace('</noscript>', '', $article_html);
$article_html = StripWithDelimiters($article_html, '<a class="clickToEnlarge', '</a>');
$article_html = stripWithDelimiters($article_html, '<span class="nowPlaying', '</span>');
$article_html = stripWithDelimiters($article_html, '<span class="duration', '</span>');
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
$article_html = stripWithDelimiters($article_html, '<svg', '</svg>');
return $article_html;
}
private function cleanArticle($article_html) {
$offset_p = strpos($article_html, '<p>');
$offset_figure = strpos($article_html, '<figure');
$offset = ($offset_figure < $offset_p ? $offset_figure : $offset_p);
$article_html = substr($article_html, $offset);
$article_html = str_replace('href="/', 'href="' . self::URI, $article_html);
$article_html = str_replace(' height="0"', '', $article_html);
$article_html = str_replace('<noscript>', '', $article_html);
$article_html = str_replace('</noscript>', '', $article_html);
$article_html = StripWithDelimiters($article_html, '<a class="clickToEnlarge', '</a>');
$article_html = stripWithDelimiters($article_html, '<span class="nowPlaying', '</span>');
$article_html = stripWithDelimiters($article_html, '<span class="duration', '</span>');
$article_html = stripWithDelimiters($article_html, '<script', '</script>');
$article_html = stripWithDelimiters($article_html, '<svg', '</svg>');
return $article_html;
}
public function collectData()
{
// Retrieve and check user input
$topic = str_replace('-', '/', $this->getInput('topic'));
if (!empty($topic) && (substr_count($topic, '/') > 1 || !ctype_alpha(str_replace('/', '', $topic)))) {
returnClientError('Invalid topic: ' . $topic);
}
public function collectData() {
// Retrieve webpage
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
$html = getSimpleHTMLDOM($pageUrl);
// Retrieve and check user input
$topic = str_replace('-', '/', $this->getInput('topic'));
if (!empty($topic) && (substr_count($topic, '/') > 1 || !ctype_alpha(str_replace('/', '', $topic))))
returnClientError('Invalid topic: ' . $topic);
// Process articles
foreach ($html->find('div.assetBody, div.riverPost') as $element) {
if (count($this->items) >= 10) {
break;
}
// Retrieve webpage
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
$html = getSimpleHTMLDOM($pageUrl);
$article_title = trim($element->find('h2, h3', 0)->plaintext);
$article_uri = self::URI . substr($element->find('a', 0)->href, 1);
$article_thumbnail = $element->parent()->find('img[src]', 0)->src;
$article_timestamp = strtotime($element->find('time.assetTime, div.timeAgo', 0)->plaintext);
$article_author = trim($element->find('a[rel=author], a.name', 0)->plaintext);
$article_content = '<p><b>' . trim($element->find('p.dek', 0)->plaintext) . '</b></p>';
// Process articles
foreach($html->find('div.assetBody, div.riverPost') as $element) {
if (is_null($article_thumbnail)) {
$article_thumbnail = extractFromDelimiters($element->innertext, '<img src="', '"');
}
if(count($this->items) >= 10) {
break;
}
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, self::URI . 'news/') !== false) {
$article_html = getSimpleHTMLDOMCached($article_uri) or $article_html = null;
$article_title = trim($element->find('h2, h3', 0)->plaintext);
$article_uri = self::URI . substr($element->find('a', 0)->href, 1);
$article_thumbnail = $element->parent()->find('img[src]', 0)->src;
$article_timestamp = strtotime($element->find('time.assetTime, div.timeAgo', 0)->plaintext);
$article_author = trim($element->find('a[rel=author], a.name', 0)->plaintext);
$article_content = '<p><b>' . trim($element->find('p.dek', 0)->plaintext) . '</b></p>';
if (!is_null($article_html)) {
if (empty($article_thumbnail)) {
$article_thumbnail = $article_html->find('div.originalImage', 0);
}
if (empty($article_thumbnail)) {
$article_thumbnail = $article_html->find('span.imageContainer', 0);
}
if (is_object($article_thumbnail)) {
$article_thumbnail = $article_thumbnail->find('img', 0)->src;
}
if (is_null($article_thumbnail))
$article_thumbnail = extractFromDelimiters($element->innertext, '<img src="', '"');
$article_content .= trim(
$this->cleanArticle(
extractFromDelimiters(
$article_html,
'<article',
'<footer'
)
)
);
}
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, self::URI . 'news/') !== false) {
$item = [];
$item['uri'] = $article_uri;
$item['title'] = $article_title;
$item['author'] = $article_author;
$item['timestamp'] = $article_timestamp;
$item['enclosures'] = [$article_thumbnail];
$item['content'] = $article_content;
$this->items[] = $item;
}
}
}
$article_html = getSimpleHTMLDOMCached($article_uri) or $article_html = null;
if (!is_null($article_html)) {
if (empty($article_thumbnail))
$article_thumbnail = $article_html->find('div.originalImage', 0);
if (empty($article_thumbnail))
$article_thumbnail = $article_html->find('span.imageContainer', 0);
if (is_object($article_thumbnail))
$article_thumbnail = $article_thumbnail->find('img', 0)->src;
$article_content .= trim(
$this->cleanArticle(
extractFromDelimiters(
$article_html, '<article', '<footer'
)
)
);
}
$item = array();
$item['uri'] = $article_uri;
$item['title'] = $article_title;
$item['author'] = $article_author;
$item['timestamp'] = $article_timestamp;
$item['enclosures'] = array($article_thumbnail);
$item['content'] = $article_content;
$this->items[] = $item;
}
}
}
}

View File

@@ -1,64 +1,63 @@
<?php
class CNETFranceBridge extends FeedExpander
{
const MAINTAINER = 'leomaradan';
const NAME = 'CNET France';
const URI = 'https://www.cnetfrance.fr/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'CNET France RSS with filters';
const PARAMETERS = [
'filters' => [
'title' => [
'name' => 'Exclude by title',
'required' => false,
'title' => 'Title term, separated by semicolon (;)',
'exampleValue' => 'bon plan;bons plans;au meilleur prix;des meilleures offres;Amazon Prime Day;RED by SFR ou B&You'
],
'url' => [
'name' => 'Exclude by url',
'required' => false,
'title' => 'URL term, separated by semicolon (;)',
'exampleValue' => 'bon-plan;bons-plans'
]
]
];
const MAINTAINER = 'leomaradan';
const NAME = 'CNET France';
const URI = 'https://www.cnetfrance.fr/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'CNET France RSS with filters';
const PARAMETERS = array(
'filters' => array(
'title' => array(
'name' => 'Exclude by title',
'required' => false,
'title' => 'Title term, separated by semicolon (;)',
'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 (;)',
'exampleValue' => 'bon-plan;bons-plans'
)
)
);
private $bannedTitle = [];
private $bannedURL = [];
private $bannedTitle = array();
private $bannedURL = array();
public function collectData()
{
$title = $this->getInput('title');
$url = $this->getInput('url');
public function collectData()
{
$title = $this->getInput('title');
$url = $this->getInput('url');
if ($title !== null) {
$this->bannedTitle = explode(';', $title);
}
if ($title !== null) {
$this->bannedTitle = explode(';', $title);
}
if ($url !== null) {
$this->bannedURL = explode(';', $url);
}
if ($url !== null) {
$this->bannedURL = explode(';', $url);
}
$this->collectExpandableDatas('https://www.cnetfrance.fr/feeds/rss/news/');
}
$this->collectExpandableDatas('https://www.cnetfrance.fr/feeds/rss/news/');
}
protected function parseItem($feedItem)
{
$item = parent::parseItem($feedItem);
protected function parseItem($feedItem)
{
$item = parent::parseItem($feedItem);
foreach ($this->bannedTitle as $term) {
if (preg_match('/' . $term . '/mi', $item['title']) === 1) {
return null;
}
}
foreach ($this->bannedTitle as $term) {
if (preg_match('/' . $term . '/mi', $item['title']) === 1) {
return null;
}
}
foreach ($this->bannedURL as $term) {
if (preg_match('/' . $term . '/mi', $item['uri']) === 1) {
return null;
}
}
foreach ($this->bannedURL as $term) {
if (preg_match('/' . $term . '/mi', $item['uri']) === 1) {
return null;
}
}
return $item;
}
return $item;
}
}

View File

@@ -7,139 +7,134 @@
// it is not reliable and contain no useful information. This bridge create a
// sane feed with additional information like tags and a link to the CWE
// a description of the vulnerability.
class CVEDetailsBridge extends BridgeAbstract
{
const MAINTAINER = 'Aaron Fischer';
const NAME = 'CVE Details';
const CACHE_TIMEOUT = 60 * 60 * 6; // 6 hours
const DESCRIPTION = 'Report new CVE vulnerabilities for a given vendor (and product)';
const URI = 'https://www.cvedetails.com';
class CVEDetailsBridge extends BridgeAbstract {
const MAINTAINER = 'Aaron Fischer';
const NAME = 'CVE Details';
const CACHE_TIMEOUT = 60 * 60 * 6; // 6 hours
const DESCRIPTION = 'Report new CVE vulnerabilities for a given vendor (and product)';
const URI = 'https://www.cvedetails.com';
const PARAMETERS = [[
// The Vendor ID can be taken from the URL
'vendor_id' => [
'name' => 'Vendor ID',
'type' => 'number',
'required' => true,
'exampleValue' => 74, // PHP
],
// The optional Product ID can be taken from the URL as well
'product_id' => [
'name' => 'Product ID',
'type' => 'number',
'required' => false,
'exampleValue' => 128, // PHP
],
]];
const PARAMETERS = array(array(
// The Vendor ID can be taken from the URL
'vendor_id' => array(
'name' => 'Vendor ID',
'type' => 'number',
'required' => true,
'exampleValue' => 74, // PHP
),
// The optional Product ID can be taken from the URL as well
'product_id' => array(
'name' => 'Product ID',
'type' => 'number',
'required' => false,
'exampleValue' => 128, // PHP
),
));
private $html = null;
private $vendor = '';
private $product = '';
private $html = null;
private $vendor = '';
private $product = '';
// Return the URL to query.
// Because of the optional product ID, we need to attach it if it is
// set. The search result page has the exact same structure (with and
// without the product ID).
private function buildUrl()
{
$url = self::URI . '/vulnerability-list/vendor_id-' . $this->getInput('vendor_id');
if ($this->getInput('product_id') !== '') {
$url .= '/product_id-' . $this->getInput('product_id');
}
// Sadly, there is no way (prove me wrong please) to sort the search
// result by publish date. So the nearest alternative is the CVE
// number, which should be mostly accurate.
$url .= '?order=1'; // Order by CVE number DESC
// Return the URL to query.
// Because of the optional product ID, we need to attach it if it is
// set. The search result page has the exact same structure (with and
// without the product ID).
private function _buildURL() {
$url = self::URI . '/vulnerability-list/vendor_id-' . $this->getInput('vendor_id');
if ($this->getInput('product_id') !== '') {
$url .= '/product_id-' . $this->getInput('product_id');
}
// Sadly, there is no way (prove me wrong please) to sort the search
// result by publish date. So the nearest alternative is the CVE
// number, which should be mostly accurate.
$url .= '?order=1'; // Order by CVE number DESC
return $url;
}
return $url;
}
// Make the actual request to cvedetails.com and stores the response
// (HTML) for later use and extract vendor and product from it.
private function fetchContent()
{
$html = getSimpleHTMLDOM($this->buildUrl());
$this->html = defaultLinkTo($html, self::URI);
// Make the actual request to cvedetails.com and stores the response
// (HTML) for later use and extract vendor and product from it.
private function _fetchContent() {
$html = getSimpleHTMLDOM($this->_buildURL());
$this->html = defaultLinkTo($html, self::URI);
$vendor = $html->find('#contentdiv > h1 > a', 0);
if ($vendor == null) {
returnServerError('Invalid Vendor ID ' .
$this->getInput('vendor_id') .
' or Product ID ' .
$this->getInput('product_id'));
}
$this->vendor = $vendor->innertext;
$vendor = $html->find('#contentdiv > h1 > a', 0);
if ($vendor == null) {
returnServerError('Invalid Vendor ID ' .
$this->getInput('vendor_id') .
' or Product ID ' .
$this->getInput('product_id'));
}
$this->vendor = $vendor->innertext;
$product = $html->find('#contentdiv > h1 > a', 1);
if ($product != null) {
$this->product = $product->innertext;
}
}
$product = $html->find('#contentdiv > h1 > a', 1);
if ($product != null) {
$this->product = $product->innertext;
}
}
// Build the name of the feed.
public function getName()
{
if ($this->getInput('vendor_id') == '') {
return self::NAME;
}
// Build the name of the feed.
public function getName() {
if ($this->getInput('vendor_id') == '') {
return self::NAME;
}
if ($this->html == null) {
$this->fetchContent();
}
if ($this->html == null) {
$this->_fetchContent();
}
$name = 'CVE Vulnerabilities for ' . $this->vendor;
if ($this->product != '') {
$name .= '/' . $this->product;
}
$name = 'CVE Vulnerabilities for ' . $this->vendor;
if ($this->product != '') {
$name .= '/' . $this->product;
}
return $name;
}
return $name;
}
// Pull the data from the HTML response and fill the items..
public function collectData()
{
if ($this->html == null) {
$this->fetchContent();
}
// Pull the data from the HTML response and fill the items..
public function collectData() {
if ($this->html == null) {
$this->_fetchContent();
}
foreach ($this->html->find('#vulnslisttable .srrowns') as $i => $tr) {
// There are some optional vulnerability types, which will be
// added to the categories as well as the CWE number -- which is
// always given.
$categories = [$this->vendor];
$enclosures = [];
foreach ($this->html->find('#vulnslisttable .srrowns') as $i => $tr) {
// There are some optional vulnerability types, which will be
// added to the categories as well as the CWE number -- which is
// always given.
$categories = array($this->vendor);
$enclosures = array();
$cwe = $tr->find('td', 2)->find('a', 0);
if ($cwe != null) {
$cwe = $cwe->innertext;
$categories[] = 'CWE-' . $cwe;
$enclosures[] = 'https://cwe.mitre.org/data/definitions/' . $cwe . '.html';
}
$c = $tr->find('td', 4)->innertext;
if (trim($c) != '') {
$categories[] = $c;
}
if ($this->product != '') {
$categories[] = $this->product;
}
$cwe = $tr->find('td', 2)->find('a', 0);
if ($cwe != null) {
$cwe = $cwe->innertext;
$categories[] = 'CWE-' . $cwe;
$enclosures[] = 'https://cwe.mitre.org/data/definitions/' . $cwe . '.html';
}
$c = $tr->find('td', 4)->innertext;
if (trim($c) != '') {
$categories[] = $c;
}
if ($this->product != '') {
$categories[] = $this->product;
}
// The CVE number itself
$title = $tr->find('td', 1)->find('a', 0)->innertext;
// The CVE number itself
$title = $tr->find('td', 1)->find('a', 0)->innertext;
$this->items[] = [
'uri' => $tr->find('td', 1)->find('a', 0)->href,
'title' => $title,
'timestamp' => $tr->find('td', 5)->innertext,
'content' => $tr->next_sibling()->innertext,
'categories' => $categories,
'enclosures' => $enclosures,
'uid' => $tr->find('td', 1)->find('a', 0)->innertext,
];
$this->items[] = array(
'uri' => $tr->find('td', 1)->find('a', 0)->href,
'title' => $title,
'timestamp' => $tr->find('td', 5)->innertext,
'content' => $tr->next_sibling()->innertext,
'categories' => $categories,
'enclosures' => $enclosures,
'uid' => $tr->find('td', 1)->find('a', 0)->innertext,
);
// We only want to fetch the latest 10 CVEs
if (count($this->items) >= 10) {
break;
}
}
}
// We only want to fetch the latest 10 CVEs
if (count($this->items) >= 10) {
break;
}
}
}
}

View File

@@ -1,138 +1,134 @@
<?php
class CachetBridge extends BridgeAbstract
{
const NAME = 'Cachet Bridge';
const URI = 'https://cachethq.io/';
const DESCRIPTION = 'Returns status updates from any Cachet installation';
const MAINTAINER = 'klimplant';
const PARAMETERS = [
[
'host' => [
'name' => 'Cachet installation',
'type' => 'text',
'required' => true,
'title' => 'The URL of the Cachet installation',
'exampleValue' => 'https://demo.cachethq.io/',
], 'additional_info' => [
'name' => 'Additional Timestamps',
'type' => 'checkbox',
'title' => 'Whether to include the given timestamps'
]
]
];
const CACHE_TIMEOUT = 300;
class CachetBridge extends BridgeAbstract {
const NAME = 'Cachet Bridge';
const URI = 'https://cachethq.io/';
const DESCRIPTION = 'Returns status updates from any Cachet installation';
const MAINTAINER = 'klimplant';
const PARAMETERS = array(
array(
'host' => array(
'name' => 'Cachet installation',
'type' => 'text',
'required' => true,
'title' => 'The URL of the Cachet installation',
'exampleValue' => 'https://demo.cachethq.io/',
), 'additional_info' => array(
'name' => 'Additional Timestamps',
'type' => 'checkbox',
'title' => 'Whether to include the given timestamps'
)
)
);
const CACHE_TIMEOUT = 300;
private $componentCache = [];
private $componentCache = array();
public function getURI()
{
return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host');
}
public function getURI() {
return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host');
}
/**
* Validates the ping request to the cache API
*
* @param string $ping
* @return boolean
*/
private function validatePing($ping)
{
$ping = json_decode($ping);
if ($ping === null) {
return false;
}
return $ping->data === 'Pong!';
}
/**
* Validates the ping request to the cache API
*
* @param string $ping
* @return boolean
*/
private function validatePing($ping) {
$ping = json_decode($ping);
if ($ping === null) {
return false;
}
return $ping->data === 'Pong!';
}
/**
* Returns the component name of a cachat component
*
* @param integer $id
* @return string
*/
private function getComponentName($id)
{
if ($id === 0) {
return '';
}
if (array_key_exists($id, $this->componentCache)) {
return $this->componentCache[$id];
}
/**
* Returns the component name of a cachat component
*
* @param integer $id
* @return string
*/
private function getComponentName($id) {
if ($id === 0) {
return '';
}
if (array_key_exists($id, $this->componentCache)) {
return $this->componentCache[$id];
}
$component = getContents($this->getURI() . '/api/v1/components/' . $id);
$component = json_decode($component);
if ($component === null) {
return '';
}
return $component->data->name;
}
$component = getContents($this->getURI() . '/api/v1/components/' . $id);
$component = json_decode($component);
if ($component === null) {
return '';
}
return $component->data->name;
}
public function collectData()
{
$ping = getContents(urljoin($this->getURI(), '/api/v1/ping'));
if (!$this->validatePing($ping)) {
returnClientError('Provided URI is invalid!');
}
public function collectData() {
$ping = getContents(urljoin($this->getURI(), '/api/v1/ping'));
if (!$this->validatePing($ping)) {
returnClientError('Provided URI is invalid!');
}
$url = urljoin($this->getURI(), '/api/v1/incidents?sort=id&order=desc');
$incidents = getContents($url);
$incidents = json_decode($incidents);
if ($incidents === null) {
returnClientError('/api/v1/incidents returned no valid json');
}
$url = urljoin($this->getURI(), '/api/v1/incidents?sort=id&order=desc');
$incidents = getContents($url);
$incidents = json_decode($incidents);
if ($incidents === null) {
returnClientError('/api/v1/incidents returned no valid json');
}
usort($incidents->data, function ($a, $b) {
$timeA = strtotime($a->updated_at);
$timeB = strtotime($b->updated_at);
return $timeA > $timeB ? -1 : 1;
});
usort($incidents->data, function ($a, $b) {
$timeA = strtotime($a->updated_at);
$timeB = strtotime($b->updated_at);
return $timeA > $timeB ? -1 : 1;
});
foreach ($incidents->data as $incident) {
if (isset($incident->permalink)) {
$permalink = $incident->permalink;
} else {
$permalink = urljoin($this->getURI(), '/incident/' . $incident->id);
}
foreach ($incidents->data as $incident) {
$title = $incident->human_status . ': ' . $incident->name;
$message = '';
if ($this->getInput('additional_info')) {
if (isset($incident->occurred_at)) {
$message .= 'Occurred at: ' . $incident->occurred_at . "\r\n";
}
if (isset($incident->scheduled_at)) {
$message .= 'Scheduled at: ' . $incident->scheduled_at . "\r\n";
}
if (isset($incident->created_at)) {
$message .= 'Created at: ' . $incident->created_at . "\r\n";
}
if (isset($incident->updated_at)) {
$message .= 'Updated at: ' . $incident->updated_at . "\r\n\r\n";
}
}
if (isset($incident->permalink)) {
$permalink = $incident->permalink;
} else {
$permalink = urljoin($this->getURI(), '/incident/' . $incident->id);
}
$message .= $incident->message;
$content = nl2br($message);
$componentName = $this->getComponentName($incident->component_id);
$uidOrig = $permalink . $incident->created_at;
$uid = hash('sha512', $uidOrig);
$timestamp = strtotime($incident->created_at);
$categories = [];
$categories[] = $incident->human_status;
if ($componentName !== '') {
$categories[] = $componentName;
}
$title = $incident->human_status . ': ' . $incident->name;
$message = '';
if ($this->getInput('additional_info')) {
if (isset($incident->occurred_at)) {
$message .= 'Occurred at: ' . $incident->occurred_at . "\r\n";
}
if (isset($incident->scheduled_at)) {
$message .= 'Scheduled at: ' . $incident->scheduled_at . "\r\n";
}
if (isset($incident->created_at)) {
$message .= 'Created at: ' . $incident->created_at . "\r\n";
}
if (isset($incident->updated_at)) {
$message .= 'Updated at: ' . $incident->updated_at . "\r\n\r\n";
}
}
$item = [];
$item['uri'] = $permalink;
$item['title'] = $title;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
$item['uid'] = $uid;
$item['categories'] = $categories;
$message .= $incident->message;
$content = nl2br($message);
$componentName = $this->getComponentName($incident->component_id);
$uidOrig = $permalink . $incident->created_at;
$uid = hash('sha512', $uidOrig);
$timestamp = strtotime($incident->created_at);
$categories = array();
$categories[] = $incident->human_status;
if ($componentName !== '') {
$categories[] = $componentName;
}
$this->items[] = $item;
}
}
$item = array();
$item['uri'] = $permalink;
$item['title'] = $title;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
$item['uid'] = $uid;
$item['categories'] = $categories;
$this->items[] = $item;
}
}
}

View File

@@ -1,44 +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';
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);
}
public function collectData()
{
$this->collectExpandableDatas('https://www.carthrottle.com/rss', 10);
}
protected function parseItem($feedItem) {
$item = parent::parseItem($feedItem);
protected function parseItem($feedItem)
{
$item = parent::parseItem($feedItem);
//fetch page
$articlePage = getSimpleHTMLDOMCached($feedItem->link)
or returnServerError('Could not retrieve ' . $feedItem->link);
//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);
$subtitle = $articlePage->find('p.standfirst', 0);
$article = $articlePage->find('div.content_field', 0);
$item['content'] = str_get_html($subtitle . $article);
$item['content'] = str_get_html($subtitle . $article);
//convert <iframe>s to <a>s. meant for embedded videos.
foreach($item['content']->find('iframe') as $found) {
//convert <iframe>s to <a>s. meant for embedded videos.
foreach ($item['content']->find('iframe') as $found) {
$iframeUrl = $found->getAttribute('src');
$iframeUrl = $found->getAttribute('src');
if ($iframeUrl) {
$found->outertext = '<a href="' . $iframeUrl . '">' . $iframeUrl . '</a>';
}
}
if ($iframeUrl) {
$found->outertext = '<a href="' . $iframeUrl . '">' . $iframeUrl . '</a>';
}
}
//remove scripts from the text
foreach ($item['content']->find('script') as $remove) {
$remove->outertext = '';
}
//remove scripts from the text
foreach ($item['content']->find('script') as $remove) {
$remove->outertext = '';
}
return $item;
}
return $item;
}
}

View File

@@ -1,77 +0,0 @@
<?php
class CaschyBridge extends FeedExpander
{
const MAINTAINER = 'Tone866';
const NAME = 'Caschys Blog Bridge';
const URI = 'https://stadt-bremerhaven.de/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns the full articles instead of only the intro';
const PARAMETERS = [[
'category' => [
'name' => 'Category',
'type' => 'list',
'values' => [
'Alle News'
=> 'https://stadt-bremerhaven.de/feed/'
]
],
'limit' => [
'name' => 'Limit',
'type' => 'number',
'required' => false,
'title' => 'Specify number of full articles to return',
'defaultValue' => 5
]
]];
const LIMIT = 5;
public function collectData()
{
$this->collectExpandableDatas(
$this->getInput('category'),
$this->getInput('limit') ?: static::LIMIT
);
}
protected function parseItem($feedItem)
{
$item = parent::parseItem($feedItem);
if (strpos($item['uri'], 'https://stadt-bremerhaven.de/') !== 0) {
return $item;
}
$article = getSimpleHTMLDOMCached($item['uri']);
if ($article) {
$article = defaultLinkTo($article, $item['uri']);
$item = $this->addArticleToItem($item, $article);
}
return $item;
}
private function addArticleToItem($item, $article)
{
// remove unwanted stuff
foreach (
$article->find('div.video-container, div.aawp, p.aawp-disclaimer, iframe.wp-embedded-content,
div.wp-embed, p.wp-caption-text, script') as $element
) {
$element->remove();
}
// reload html, as remove() is buggy
$article = str_get_html($article->outertext);
$categories = $article->find('div.post-category a');
foreach ($categories as $category) {
$item['categories'][] = $category->plaintext;
}
$content = $article->find('div.entry-inner', 0);
$item['content'] = $content;
return $item;
}
}

View File

@@ -1,135 +1,118 @@
<?php
class CastorusBridge extends BridgeAbstract {
const MAINTAINER = 'logmanoriginal';
const NAME = 'Castorus Bridge';
const URI = 'https://www.castorus.com';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'Returns the latest changes';
class CastorusBridge extends BridgeAbstract
{
const MAINTAINER = 'logmanoriginal';
const NAME = 'Castorus Bridge';
const URI = 'https://www.castorus.com';
const CACHE_TIMEOUT = 600; // 10min
const DESCRIPTION = 'Returns the latest changes';
const PARAMETERS = array(
'Get latest changes' => array(),
'Get latest changes via ZIP code' => array(
'zip' => array(
'name' => 'ZIP code',
'type' => 'text',
'required' => true,
'exampleValue' => '7',
'title' => 'Insert ZIP code (complete or partial). e.g: 78125 OR 781 OR 7'
)
),
'Get latest changes via city name' => array(
'city' => array(
'name' => 'City name',
'type' => 'text',
'required' => true,
'exampleValue' => 'Paris',
'title' => 'Insert city name (complete or partial). e.g: Paris OR Par OR P'
)
)
);
const PARAMETERS = [
'Get latest changes' => [],
'Get latest changes via ZIP code' => [
'zip' => [
'name' => 'ZIP code',
'type' => 'text',
'required' => true,
'exampleValue' => '7',
'title' => 'Insert ZIP code (complete or partial). e.g: 78125 OR 781 OR 7'
]
],
'Get latest changes via city name' => [
'city' => [
'name' => 'City name',
'type' => 'text',
'required' => true,
'exampleValue' => 'Paris',
'title' => 'Insert city name (complete or partial). e.g: Paris OR Par OR P'
]
]
];
// Extracts the title from an actitiy
private function extractActivityTitle($activity){
$title = $activity->find('a', 0);
// Extracts the title from an actitiy
private function extractActivityTitle($activity)
{
$title = $activity->find('a', 0);
if(!$title)
returnServerError('Cannot find title!');
if (!$title) {
returnServerError('Cannot find title!');
}
return htmlspecialchars(trim($title->plaintext));
}
return trim($title->plaintext);
}
// Extracts the url from an actitiy
private function extractActivityUrl($activity){
$url = $activity->find('a', 0);
// Extracts the url from an actitiy
private function extractActivityUrl($activity)
{
$url = $activity->find('a', 0);
if(!$url)
returnServerError('Cannot find url!');
if (!$url) {
returnServerError('Cannot find url!');
}
return self::URI . $url->href;
}
return self::URI . $url->href;
}
// Extracts the time from an activity
private function extractActivityTime($activity){
// Unfortunately the time is part of the parent node,
// so we have to clear all child nodes first
$nodes = $activity->find('*');
// Extracts the time from an activity
private function extractActivityTime($activity)
{
// Unfortunately the time is part of the parent node,
// so we have to clear all child nodes first
$nodes = $activity->find('*');
if(!$nodes)
returnServerError('Cannot find nodes!');
if (!$nodes) {
returnServerError('Cannot find nodes!');
}
foreach($nodes as $node) {
$node->outertext = '';
}
foreach ($nodes as $node) {
$node->outertext = '';
}
return strtotime($activity->innertext);
}
return strtotime($activity->innertext);
}
// Extracts the price change
private function extractActivityPrice($activity){
$price = $activity->find('span', 1);
// Extracts the price change
private function extractActivityPrice($activity)
{
$price = $activity->find('span', 1);
if(!$price)
returnServerError('Cannot find price!');
if (!$price) {
returnServerError('Cannot find price!');
}
return $price->innertext;
}
return $price->innertext;
}
public function collectData(){
$zip_filter = trim($this->getInput('zip'));
$city_filter = trim($this->getInput('city'));
public function collectData()
{
$zip_filter = trim($this->getInput('zip'));
$city_filter = trim($this->getInput('city'));
$html = getSimpleHTMLDOM(self::URI);
$html = getSimpleHTMLDOM(self::URI);
if(!$html)
returnServerError('Could not load data from ' . self::URI . '!');
if (!$html) {
returnServerError('Could not load data from ' . self::URI . '!');
}
$activities = $html->find('div#activite > li');
$activities = $html->find('div#activite > li');
if(!$activities)
returnServerError('Failed to find activities!');
if (!$activities) {
returnServerError('Failed to find activities!');
}
foreach($activities as $activity) {
$item = array();
foreach ($activities as $activity) {
$item = [];
$item['title'] = $this->extractActivityTitle($activity);
$item['uri'] = $this->extractActivityUrl($activity);
$item['timestamp'] = $this->extractActivityTime($activity);
$item['content'] = '<a href="'
. $item['uri']
. '">'
. $item['title']
. '</a><br><p>'
. $this->extractActivityPrice($activity)
. '</p>';
$item['title'] = $this->extractActivityTitle($activity);
$item['uri'] = $this->extractActivityUrl($activity);
$item['timestamp'] = $this->extractActivityTime($activity);
$item['content'] = '<a href="'
. $item['uri']
. '">'
. $item['title']
. '</a><br><p>'
. $this->extractActivityPrice($activity)
. '</p>';
if(isset($zip_filter)
&& !(substr($item['title'], 0, strlen($zip_filter)) === $zip_filter)) {
continue; // Skip this item
}
if (
isset($zip_filter)
&& !(substr($item['title'], 0, strlen($zip_filter)) === $zip_filter)
) {
continue; // Skip this item
}
if(isset($city_filter)
&& !(substr($item['title'], strpos($item['title'], ' ') + 1, strlen($city_filter)) === $city_filter)) {
continue; // Skip this item
}
if (
isset($city_filter)
&& !(substr($item['title'], strpos($item['title'], ' ') + 1, strlen($city_filter)) === $city_filter)
) {
continue; // Skip this item
}
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
}

View File

@@ -1,62 +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 = [ [
'category' => [
'name' => 'Kategoria',
'type' => 'list',
'values' => [
'Najnowsze (wszystkie)' => 'najnowsze',
'Newsy' => 'newsy',
'Recenzje' => 'recenzje',
'Teksty' => [
'Publicystyka' => 'publicystyka',
'Zapowiedzi' => 'zapowiedzi',
'Już graliśmy' => 'juz-gralismy',
'Poradniki' => 'poradniki',
],
'Kultura' => 'kultura',
'Wideo' => 'wideo',
'Czasopismo' => 'czasopismo',
'Technologie' => [
'Artykuły' => 'artykuly',
'Testy' => 'testy',
],
'Na luzie' => [
'Konkursy' => 'konkursy',
'Nadgodziny' => 'nadgodziny',
]
]
]]
];
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'));
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI() . '/' . $this->getInput('category'));
$newsJson = $html->find('script#__NEXT_DATA__', 0)->innertext;
if (!$newsJson = json_decode($newsJson)) {
return;
}
$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 = [];
$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;
$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;
}
}
$this->items[] = $item;
}
}
}

View File

@@ -1,88 +1,83 @@
<?php
class CeskaTelevizeBridge extends BridgeAbstract
{
const NAME = 'Česká televize Bridge';
const URI = 'https://www.ceskatelevize.cz';
const CACHE_TIMEOUT = 3600;
const DESCRIPTION = 'Return newest videos';
const MAINTAINER = 'kolarcz';
class CeskaTelevizeBridge extends BridgeAbstract {
const PARAMETERS = [
[
'url' => [
'name' => 'url to the show',
'required' => true,
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/'
]
]
];
const NAME = 'Česká televize Bridge';
const URI = 'https://www.ceskatelevize.cz';
const CACHE_TIMEOUT = 3600;
const DESCRIPTION = 'Return newest videos';
const MAINTAINER = 'kolarcz';
private function fixChars($text)
{
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
}
const PARAMETERS = array(
array(
'url' => array(
'name' => 'url to the show',
'required' => true,
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/'
)
)
);
private function getUploadTimeFromString($string)
{
if (strpos($string, 'dnes') !== false) {
return strtotime('today');
} elseif (strpos($string, 'včera') !== false) {
return strtotime('yesterday');
} elseif (!preg_match('/(\d+).\s(\d+).(\s(\d+))?/', $string, $match)) {
returnServerError('Could not get date from Česká televize string');
}
private function fixChars($text) {
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
}
$date = sprintf('%04d-%02d-%02d', $match[3] ?? date('Y'), $match[2], $match[1]);
return strtotime($date);
}
private function getUploadTimeFromString($string) {
if (strpos($string, 'dnes') !== false) {
return strtotime('today');
} elseif (strpos($string, 'včera') !== false) {
return strtotime('yesterday');
} elseif (!preg_match('/(\d+).\s(\d+).(\s(\d+))?/', $string, $match)) {
returnServerError('Could not get date from Česká televize string');
}
public function collectData()
{
$url = $this->getInput('url');
$date = sprintf('%04d-%02d-%02d', isset($match[3]) ? $match[3] : date('Y'), $match[2], $match[1]);
return strtotime($date);
}
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(bonus\/)?$/';
if (!preg_match($validUrl, $url, $match)) {
returnServerError('Invalid url');
}
public function collectData() {
$url = $this->getInput('url');
$category = $match[4] ?? 'nove';
$fixedUrl = "{$match[1]}dily/{$category}/";
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(bonus\/)?$/';
if (!preg_match($validUrl, $url, $match)) {
returnServerError('Invalid url');
}
$html = getSimpleHTMLDOM($fixedUrl);
$category = isset($match[4]) ? $match[4] : 'nove';
$fixedUrl = "{$match[1]}dily/{$category}/";
$this->feedUri = $fixedUrl;
$this->feedName = str_replace('Přehled dílů — ', '', $this->fixChars($html->find('title', 0)->plaintext));
if ($category !== 'nove') {
$this->feedName .= " ({$category})";
}
$html = getSimpleHTMLDOM($fixedUrl);
foreach ($html->find('#episodeListSection a[data-testid=next-link]') as $element) {
$itemTitle = $element->find('h3', 0);
$itemContent = $element->find('div[class^=content-]', 0);
$itemDate = $element->find('div[class^=playTime-] span', 0);
$itemThumbnail = $element->find('img', 0);
$itemUri = self::URI . $element->getAttribute('href');
$this->feedUri = $fixedUrl;
$this->feedName = str_replace('Přehled dílů — ', '', $this->fixChars($html->find('title', 0)->plaintext));
if ($category !== 'nove') {
$this->feedName .= " ({$category})";
}
$item = [
'title' => $this->fixChars($itemTitle->plaintext),
'uri' => $itemUri,
'content' => '<img src="' . $itemThumbnail->getAttribute('src') . '" /><br />'
. $this->fixChars($itemContent->plaintext),
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext)
];
foreach ($html->find('#episodeListSection a[data-testid=next-link]') as $element) {
$itemTitle = $element->find('h3', 0);
$itemContent = $element->find('div[class^=content-]', 0);
$itemDate = $element->find('div[class^=playTime-] span', 0);
$itemThumbnail = $element->find('img', 0);
$itemUri = self::URI . $element->getAttribute('href');
$this->items[] = $item;
}
}
$item = array(
'title' => $this->fixChars($itemTitle->plaintext),
'uri' => $itemUri,
'content' => '<img src="' . $itemThumbnail->getAttribute('src') . '" /><br />'
. $this->fixChars($itemContent->plaintext),
'timestamp' => $this->getUploadTimeFromString($itemDate->plaintext)
);
public function getURI()
{
return $this->feedUri ?? parent::getURI();
}
$this->items[] = $item;
}
}
public function getName()
{
return $this->feedName ?? parent::getName();
}
public function getURI() {
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
}
public function getName() {
return isset($this->feedName) ? $this->feedName : parent::getName();
}
}

View File

@@ -1,314 +1,346 @@
<?php
class CodebergBridge extends BridgeAbstract {
const NAME = 'Codeberg Bridge';
const URI = 'https://codeberg.org/';
const DESCRIPTION = 'Returns commits, issues, pull requests or releases for a repository.';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
'Commits' => array(
'branch' => array(
'name' => 'branch',
'type' => 'text',
'exampleValue' => 'main',
'required' => false,
'title' => 'Optional, main branch is used by default.',
),
),
'Issues' => array(),
'Issue Comments' => array(
'issueId' => array(
'name' => 'Issue ID',
'type' => 'text',
'required' => true,
'exampleValue' => '513',
)
),
'Pull Requests' => array(),
'Releases' => array(),
'global' => array(
'username' => array(
'name' => 'Username',
'type' => 'text',
'exampleValue' => 'Codeberg',
'title' => 'Username of account that the repository belongs to.',
'required' => true,
),
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'exampleValue' => 'Community',
'required' => true,
)
)
);
class CodebergBridge extends BridgeAbstract
{
const NAME = 'Codeberg Bridge';
const URI = 'https://codeberg.org/';
const DESCRIPTION = 'Returns commits, issues, pull requests or releases for a repository.';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = [
'Commits' => [
'branch' => [
'name' => 'branch',
'type' => 'text',
'exampleValue' => 'main',
'required' => false,
'title' => 'Optional, main branch is used by default.',
],
],
'Issues' => [],
'Issue Comments' => [
'issueId' => [
'name' => 'Issue ID',
'type' => 'text',
'required' => true,
'exampleValue' => '513',
]
],
'Pull Requests' => [],
'Releases' => [],
'Tags' => [],
'global' => [
'username' => [
'name' => 'Username',
'type' => 'text',
'exampleValue' => 'Codeberg',
'title' => 'Username of account that the repository belongs to.',
'required' => true,
],
'repo' => [
'name' => 'Repository',
'type' => 'text',
'exampleValue' => 'Community',
'required' => true,
]
]
];
const CACHE_TIMEOUT = 1800;
const CACHE_TIMEOUT = 1800;
const TEST_DETECT_PARAMETERS = array(
'https://codeberg.org/Codeberg/Community/issues/507' => array(
'context' => 'Issue Comments', 'username' => 'Codeberg', 'repo' => 'Community', 'issueId' => '507'
),
'https://codeberg.org/Codeberg/Community/issues' => array(
'context' => 'Issues', 'username' => 'Codeberg', 'repo' => 'Community'
),
'https://codeberg.org/Codeberg/Community/pulls' => array(
'context' => 'Pull Requests', 'username' => 'Codeberg', 'repo' => 'Community'
),
'https://codeberg.org/Codeberg/Community/releases' => array(
'context' => 'Releases', 'username' => 'Codeberg', 'repo' => 'Community'
),
'https://codeberg.org/Codeberg/Community/commits/branch/master' => array(
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community', 'branch' => 'master'
),
'https://codeberg.org/Codeberg/Community/commits' => array(
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community'
)
);
const TEST_DETECT_PARAMETERS = [
'https://codeberg.org/Codeberg/Community/issues/507' => [
'context' => 'Issue Comments', 'username' => 'Codeberg', 'repo' => 'Community', 'issueId' => '507'
],
'https://codeberg.org/Codeberg/Community/issues' => [
'context' => 'Issues', 'username' => 'Codeberg', 'repo' => 'Community'
],
'https://codeberg.org/Codeberg/Community/pulls' => [
'context' => 'Pull Requests', 'username' => 'Codeberg', 'repo' => 'Community'
],
'https://codeberg.org/Codeberg/Community/releases' => [
'context' => 'Releases', 'username' => 'Codeberg', 'repo' => 'Community'
],
'https://codeberg.org/Codeberg/Community/commits/branch/master' => [
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community', 'branch' => 'master'
],
'https://codeberg.org/Codeberg/Community/commits' => [
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community'
]
];
private $defaultBranch = 'main';
private $issueTitle = '';
private $defaultBranch = 'main';
private $issueTitle = '';
private $urlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)(?:\/commits\/branch\/([\w]+))?/';
private $issuesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues/';
private $pullsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/pulls/';
private $releasesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/releases/';
private $issueCommentsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues\/([0-9]+)/';
private $urlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)(?:\/commits\/branch\/([\w]+))?/';
private $issuesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues/';
private $pullsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/pulls/';
private $releasesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/releases/';
private $issueCommentsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues\/([0-9]+)/';
public function detectParameters($url) {
$params = array();
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI());
// Issue Comments
if(preg_match($this->issueCommentsUrlRegex, $url, $matches)) {
$params['context'] = 'Issue Comments';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
$params['issueId'] = $matches[3];
$html = defaultLinkTo($html, $this->getURI());
return $params;
}
switch ($this->queriedContext) {
case 'Commits':
$this->extractCommits($html);
break;
case 'Issues':
$this->extractIssues($html);
break;
case 'Issue Comments':
$this->extractIssueComments($html);
break;
case 'Pull Requests':
$this->extractPulls($html);
break;
case 'Releases':
$this->extractReleases($html);
break;
case 'Tags':
$this->extractTags($html);
break;
default:
throw new \Exception('Invalid context: ' . $this->queriedContext);
}
}
// Issues
if(preg_match($this->issuesUrlRegex, $url, $matches)) {
$params['context'] = 'Issues';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
public function getName()
{
switch ($this->queriedContext) {
case 'Commits':
if ($this->getBranch() === $this->defaultBranch) {
return $this->getRepo() . ' Commits';
}
return $params;
}
return $this->getRepo() . ' Commits (' . $this->getBranch() . ' branch) - ' . self::NAME;
case 'Issues':
return $this->getRepo() . ' Issues - ' . self::NAME;
case 'Issue Comments':
return $this->issueTitle . ' - Issue Comments - ' . self::NAME;
case 'Pull Requests':
return $this->getRepo() . ' Pull Requests - ' . self::NAME;
case 'Releases':
return $this->getRepo() . ' Releases - ' . self::NAME;
case 'Tags':
return $this->getRepo() . ' Tags - ' . self::NAME;
default:
return parent::getName();
}
}
// Pull Requests
if(preg_match($this->pullsUrlRegex, $url, $matches)) {
$params['context'] = 'Pull Requests';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
public function getURI()
{
switch ($this->queriedContext) {
case 'Commits':
return self::URI . $this->getRepo() . '/commits/branch/' . $this->getBranch();
case 'Issues':
return self::URI . $this->getRepo() . '/issues/';
case 'Issue Comments':
return self::URI . $this->getRepo() . '/issues/' . $this->getInput('issueId');
case 'Pull Requests':
return self::URI . $this->getRepo() . '/pulls';
case 'Releases':
return self::URI . $this->getRepo() . '/releases';
case 'Tags':
return self::URI . $this->getRepo() . '/tags';
default:
return parent::getURI();
}
}
return $params;
}
private function getBranch()
{
if ($this->getInput('branch')) {
return $this->getInput('branch');
}
// Releases
if(preg_match($this->releasesUrlRegex, $url, $matches)) {
$params['context'] = 'Releases';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
return $this->defaultBranch;
}
return $params;
}
private function getRepo()
{
return $this->getInput('username') . '/' . $this->getInput('repo');
}
// Commits
if(preg_match($this->urlRegex, $url, $matches)) {
$params['context'] = 'Commits';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
/**
* Extract commits
*/
private function extractCommits($html)
{
$table = $html->find('table#commits-table', 0);
$tbody = $table->find('tbody.commit-list', 0);
if (isset($matches[3])) {
$params['branch'] = $matches[3];
}
foreach ($tbody->find('tr') as $tr) {
$item = [];
return $params;
}
$message = $tr->find('td.message', 0);
return null;
}
$item['title'] = $message->find('span.message-wrapper', 0)->plaintext;
$item['uri'] = $tr->find('td.sha', 0)->find('a', 0)->href;
$item['author'] = $tr->find('td.author', 0)->plaintext;
$item['timestamp'] = $tr->find('td', 3)->find('span', 0)->title;
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI());
if ($message->find('pre.commit-body', 0)) {
$message->find('pre.commit-body', 0)->style = '';
$html = defaultLinkTo($html, $this->getURI());
$item['content'] = $message->find('pre.commit-body', 0);
} else {
$item['content'] = '<blockquote>' . $item['title'] . '</blockquote>';
}
switch($this->queriedContext) {
case 'Commits':
$this->extractCommits($html);
break;
case 'Issues':
$this->extractIssues($html);
break;
case 'Issue Comments':
$this->extractIssueComments($html);
break;
case 'Pull Requests':
$this->extractPulls($html);
break;
case 'Releases':
$this->extractReleases($html);
break;
default:
returnClientError('Invalid context: ' . $this->queriedContext);
}
}
$this->items[] = $item;
}
}
public function getName() {
switch($this->queriedContext) {
case 'Commits':
if ($this->getBranch() === $this->defaultBranch) {
return $this->getRepo() . ' Commits';
}
/**
* Extract issues
*/
private function extractIssues($html)
{
$div = $html->find('div.issue.list', 0);
return $this->getRepo() . ' Commits (' . $this->getBranch() . ' branch) - ' . self::NAME;
case 'Issues':
return $this->getRepo() . ' Issues - ' . self::NAME;
case 'Issue Comments':
return $this->issueTitle . ' - Issue Comments - ' . self::NAME;
case 'Pull Requests':
return $this->getRepo() . ' Pull Requests - ' . self::NAME;
case 'Releases':
return $this->getRepo() . ' Releases - ' . self::NAME;
default:
return parent::getName();
}
}
foreach ($div->find('li.item') as $li) {
$item = [];
public function getURI() {
switch($this->queriedContext) {
case 'Commits':
return self::URI . $this->getRepo() . '/commits/branch/' . $this->getBranch();
case 'Issues':
return self::URI . $this->getRepo() . '/issues/';
case 'Issue Comments':
return self::URI . $this->getRepo() . '/issues/' . $this->getInput('issueId');
case 'Pull Requests':
return self::URI . $this->getRepo() . '/pulls';
case 'Releases':
return self::URI . $this->getRepo() . '/releases';
default:
return parent::getURI();
}
}
$number = trim($li->find('a.index,ml-0.mr-2', 0)->plaintext);
private function getBranch() {
if ($this->getInput('branch')) {
return $this->getInput('branch');
}
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
$item['uri'] = $li->find('a.title', 0)->href;
$item['timestamp'] = $li->find('span.time-since', 0)->title;
$item['author'] = $li->find('div.desc', 0)->find('a', 1)->plaintext;
return $this->defaultBranch;
}
// Fetch issue page
$issuePage = getSimpleHTMLDOMCached($item['uri'], 3600);
$issuePage = defaultLinkTo($issuePage, self::URI);
private function getRepo() {
return $this->getInput('username') . '/' . $this->getInput('repo');
}
$item['content'] = $issuePage->find('div.timeline-item.comment.first', 0)->find('div.render-content.markup', 0);
/**
* Extract commits
*/
private function extractCommits($html) {
$table = $html->find('table#commits-table', 0);
$tbody = $table->find('tbody.commit-list', 0);
foreach ($li->find('a.ui.label') as $label) {
$item['categories'][] = $label->plaintext;
}
foreach ($tbody->find('tr') as $tr) {
$item = array();
$this->items[] = $item;
}
}
$message = $tr->find('td.message', 0);
/**
* Extract issue comments
*/
private function extractIssueComments($html)
{
$this->issueTitle = $html->find('span#issue-title', 0)->plaintext
. ' (' . $html->find('span.index', 0)->plaintext . ')';
$item['title'] = $message->find('span.message-wrapper', 0)->plaintext;
$item['uri'] = $tr->find('td.sha', 0)->find('a', 0)->href;
$item['author'] = $tr->find('td.author', 0)->plaintext;
$item['timestamp'] = $tr->find('td', 3)->find('span', 0)->title;
foreach ($html->find('div.timeline-item.comment') as $div) {
$item = [];
if ($message->find('pre.commit-body', 0)) {
$message->find('pre.commit-body', 0)->style = '';
if ($div->class === 'timeline-item comment merge box') {
continue;
}
$item['content'] = $message->find('pre.commit-body', 0);
} else {
$item['content'] = '<blockquote>' . $item['title'] . '</blockquote>';
}
$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.markup', 0);
$this->items[] = $item;
}
}
if ($div->find('div.dropzone-attachments', 0)) {
$item['content'] .= $div->find('div.dropzone-attachments', 0);
}
/**
* Extract issues
*/
private function extractIssues($html) {
$div = $html->find('div.issue.list', 0);
$item['author'] = $div->find('a.author', 0)->innertext;
$item['timestamp'] = $div->find('span.time-since', 0)->title;
foreach ($div->find('li.item') as $li) {
$item = array();
$this->items[] = $item;
}
}
$number = trim($li->find('a.index,ml-0.mr-2', 0)->plaintext);
/**
* Extract pulls
*/
private function extractPulls($html)
{
$div = $html->find('div.issue.list', 0);
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
$item['uri'] = $li->find('a.title', 0)->href;
$item['timestamp'] = $li->find('span.time-since', 0)->title;
$item['author'] = $li->find('div.desc', 0)->find('a', 1)->plaintext;
foreach ($div->find('li.item') as $li) {
$item = [];
// Fetch issue page
$issuePage = getSimpleHTMLDOMCached($item['uri'], 3600);
$issuePage = defaultLinkTo($issuePage, self::URI);
$number = trim($li->find('a.index,ml-0.mr-2', 0)->plaintext);
$item['content'] = $issuePage->find('div.timeline-item.comment.first', 0)->find('div.render-content.markup', 0);
$item['title'] = $li->find('a.title', 0)->plaintext . ' (' . $number . ')';
$item['uri'] = $li->find('a.title', 0)->href;
$item['timestamp'] = $li->find('span.time-since', 0)->title;
$item['author'] = $li->find('div.desc', 0)->find('a', 1)->plaintext;
foreach ($li->find('a.ui.label') as $label) {
$item['categories'][] = $label->plaintext;
}
// Fetch pull request page
$pullRequestPage = getSimpleHTMLDOMCached($item['uri'], 3600);
$pullRequestPage = defaultLinkTo($pullRequestPage, self::URI);
$this->items[] = $item;
}
}
$item['content'] = $pullRequestPage->find('ui.timeline', 0)->find('div.render-content.markup', 0);
/**
* Extract issue comments
*/
private function extractIssueComments($html) {
$this->issueTitle = $html->find('span#issue-title', 0)->plaintext
. ' (' . $html->find('span.index', 0)->plaintext . ')';
foreach ($li->find('a.ui.label') as $label) {
$item['categories'][] = $label->plaintext;
}
foreach ($html->find('div.timeline-item.comment') as $div) {
$item = array();
$this->items[] = $item;
}
}
if ($div->class === 'timeline-item comment merge box') {
continue;
}
/**
* Extract releases
*/
private function extractReleases($html)
{
$ul = $html->find('ul#release-list', 0);
$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.markup', 0);
$lis = $ul->find('li.ui.grid');
if ($lis === []) {
throw new \Exception('Found zero releases');
}
foreach ($lis as $li) {
$item = [];
$item['title'] = $li->find('h4', 0)->plaintext;
$item['uri'] = $li->find('h4', 0)->find('a', 0)->href;
if ($div->find('div.dropzone-attachments', 0)) {
$item['content'] .= $div->find('div.dropzone-attachments', 0);
}
$tag = $this->stripSvg($li->find('span.tag', 0));
$commit = $this->stripSvg($li->find('span.commit', 0));
$downloads = $this->extractDownloads($li->find('details.download', 0));
$item['author'] = $div->find('a.author', 0)->innertext;
$item['timestamp'] = $div->find('span.time-since', 0)->title;
$item['content'] = $li->find('div.markup.desc', 0);
$item['content'] .= <<<HTML
$this->items[] = $item;
}
}
/**
* Extract pulls
*/
private function extractPulls($html) {
$div = $html->find('div.issue.list', 0);
foreach ($div->find('li.item') as $li) {
$item = array();
$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('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.markup', 0);
foreach ($li->find('a.ui.label') as $label) {
$item['categories'][] = $label->plaintext;
}
$this->items[] = $item;
}
}
/**
* 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;
$tag = $this->stripSvg($li->find('span.tag', 0));
$commit = $this->stripSvg($li->find('span.commit', 0));
$downloads = $this->extractDownloads($li->find('details.download', 0));
$item['content'] = $li->find('div.markup.desc', 0);
$item['content'] .= <<<HTML
<strong>Tag</strong>
<p>{$tag}</p>
<strong>Commit</strong>
@@ -316,131 +348,56 @@ class CodebergBridge extends BridgeAbstract
{$downloads}
HTML;
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
$item['author'] = $li->find('span.author', 0)->find('a', 0)->plaintext;
$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;
}
}
$this->items[] = $item;
}
}
private function extractTags($html)
{
$tags = $html->find('td.tag');
if ($tags === []) {
throw new \Exception('Found zero tags');
}
foreach ($tags as $tag) {
$this->items[] = [
'title' => $tag->find('a', 0)->plaintext,
'uri' => $tag->find('a', 0)->href,
'content' => $tag->innertext,
];
}
}
/**
* Extract downloads for a releases
*/
private function extractDownloads($html, $skipFirst = false) {
$downloads = '';
/**
* Extract downloads for a releases
*/
private function extractDownloads($html, $skipFirst = false)
{
$downloads = '';
foreach ($html->find('a') as $index => $a) {
if ($skipFirst === true && $index === 0) {
continue;
}
foreach ($html->find('a') as $index => $a) {
if ($skipFirst === true && $index === 0) {
continue;
}
$downloads .= <<<HTML
$downloads .= <<<HTML
<a href="{$a->herf}">{$a->plaintext}</a><br>
HTML;
}
}
return <<<EOD
return <<<EOD
<strong>Downloads</strong>
<p>{$downloads}</p>
EOD;
}
}
/**
* Ellipsis title to first 100 characters
*/
private function ellipsisTitle($text)
{
$length = 100;
/**
* Ellipsis title to first 100 characters
*/
private function ellipsisTitle($text) {
$length = 100;
if (strlen($text) > $length) {
$text = explode('<br>', wordwrap($text, $length, '<br>'));
return $text[0] . '...';
}
return $text;
}
if (strlen($text) > $length) {
$text = explode('<br>', wordwrap($text, $length, '<br>'));
return $text[0] . '...';
}
return $text;
}
/**
* Strip SVG tag
*/
private function stripSvg($html)
{
if ($html->find('svg', 0)) {
$html->find('svg', 0)->outertext = '';
}
/**
* Strip SVG tag
*/
private function stripSvg($html) {
if ($html->find('svg', 0)) {
$html->find('svg', 0)->outertext = '';
}
return $html;
}
public function detectParameters($url)
{
$params = [];
// Issue Comments
if (preg_match($this->issueCommentsUrlRegex, $url, $matches)) {
$params['context'] = 'Issue Comments';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
$params['issueId'] = $matches[3];
return $params;
}
// Issues
if (preg_match($this->issuesUrlRegex, $url, $matches)) {
$params['context'] = 'Issues';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// Pull Requests
if (preg_match($this->pullsUrlRegex, $url, $matches)) {
$params['context'] = 'Pull Requests';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// Releases
if (preg_match($this->releasesUrlRegex, $url, $matches)) {
$params['context'] = 'Releases';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// Commits
if (preg_match($this->urlRegex, $url, $matches)) {
$params['context'] = 'Commits';
$params['username'] = $matches[1];
$params['repo'] = $matches[2];
if (isset($matches[3])) {
$params['branch'] = $matches[3];
}
return $params;
}
return null;
}
return $html;
}
}

View File

@@ -1,85 +1,83 @@
<?php
class CollegeDeFranceBridge extends BridgeAbstract {
class CollegeDeFranceBridge extends BridgeAbstract
{
const MAINTAINER = 'pit-fgfjiudghdf';
const NAME = 'CollegeDeFrance';
const URI = 'https://www.college-de-france.fr/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the latest audio and video from CollegeDeFrance';
const MAINTAINER = 'pit-fgfjiudghdf';
const NAME = 'CollegeDeFrance';
const URI = 'https://www.college-de-france.fr/';
const CACHE_TIMEOUT = 10800; // 3h
const DESCRIPTION = 'Returns the latest audio and video from CollegeDeFrance';
public function collectData()
{
$months = [
'01' => 'janv.',
'02' => 'févr.',
'03' => 'mars',
'04' => 'avr.',
'05' => 'mai',
'06' => 'juin',
'07' => 'juil.',
'08' => 'août',
'09' => 'sept.',
'10' => 'oct.',
'11' => 'nov.',
'12' => 'déc.'
];
public function collectData(){
$months = array(
'01' => 'janv.',
'02' => 'févr.',
'03' => 'mars',
'04' => 'avr.',
'05' => 'mai',
'06' => 'juin',
'07' => 'juil.',
'08' => 'août',
'09' => 'sept.',
'10' => 'oct.',
'11' => 'nov.',
'12' => 'déc.'
);
// The "API" used by the site returns a list of partial HTML in this form
/* <li>
* <a href="/site/thomas-romer/guestlecturer-2016-04-15-14h30.htm" data-target="after">
* <span class="date"><span class="list-icon list-icon-video"></span>
* <span class="list-icon list-icon-audio"></span>15 avr. 2016</span>
* <span class="lecturer">Christopher Hays</span>
* <span class='title'>Imagery of Divine Suckling in the Hebrew Bible and the Ancient Near East</span>
* </a>
* </li>
*/
$html = getSimpleHTMLDOM(self::URI
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all');
// The "API" used by the site returns a list of partial HTML in this form
/* <li>
* <a href="/site/thomas-romer/guestlecturer-2016-04-15-14h30.htm" data-target="after">
* <span class="date"><span class="list-icon list-icon-video"></span>
* <span class="list-icon list-icon-audio"></span>15 avr. 2016</span>
* <span class="lecturer">Christopher Hays</span>
* <span class='title'>Imagery of Divine Suckling in the Hebrew Bible and the Ancient Near East</span>
* </a>
* </li>
*/
$html = getSimpleHTMLDOM(self::URI
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all');
foreach ($html->find('a[data-target]') as $element) {
$item = [];
$item['title'] = $element->find('.title', 0)->plaintext;
foreach($html->find('a[data-target]') as $element) {
$item = array();
$item['title'] = $element->find('.title', 0)->plaintext;
// Most relative URLs contains an hour in addition to the date, so let's use it
// <a href="/site/yann-lecun/course-2016-04-08-11h00.htm" data-target="after">
//
// Sometimes there's an __1, perhaps it signifies an update
// "/site/patrick-boucheron/seminar-2016-05-03-18h00__1.htm"
//
// But unfortunately some don't have any hours info
// <a href="/site/institut-physique/
// The-Mysteries-of-Decoherence-Sebastien-Gleyzes-[Video-3-35].htm" data-target="after">
$timezone = new DateTimeZone('Europe/Paris');
// Most relative URLs contains an hour in addition to the date, so let's use it
// <a href="/site/yann-lecun/course-2016-04-08-11h00.htm" data-target="after">
//
// Sometimes there's an __1, perhaps it signifies an update
// "/site/patrick-boucheron/seminar-2016-05-03-18h00__1.htm"
//
// But unfortunately some don't have any hours info
// <a href="/site/institut-physique/
// The-Mysteries-of-Decoherence-Sebastien-Gleyzes-[Video-3-35].htm" data-target="after">
$timezone = new DateTimeZone('Europe/Paris');
// strpos($element->href, '201') will break in 2020 but it'll
// probably break prior to then due to site changes anyway
$d = DateTime::createFromFormat(
'!Y-m-d-H\hi',
substr($element->href, strpos($element->href, '201'), 16),
$timezone
);
// strpos($element->href, '201') will break in 2020 but it'll
// probably break prior to then due to site changes anyway
$d = DateTime::createFromFormat(
'!Y-m-d-H\hi',
substr($element->href, strpos($element->href, '201'), 16),
$timezone
);
if (!$d) {
$d = DateTime::createFromFormat(
'!d m Y',
trim(str_replace(
array_values($months),
array_keys($months),
$element->find('.date', 0)->plaintext
)),
$timezone
);
}
if(!$d) {
$d = DateTime::createFromFormat(
'!d m Y',
trim(str_replace(
array_values($months),
array_keys($months),
$element->find('.date', 0)->plaintext
)),
$timezone
);
}
$item['timestamp'] = $d->format('U');
$item['content'] = $element->find('.lecturer', 0)->innertext
. ' - '
. $element->find('.title', 0)->innertext;
$item['timestamp'] = $d->format('U');
$item['content'] = $element->find('.lecturer', 0)->innertext
. ' - '
. $element->find('.title', 0)->innertext;
$item['uri'] = self::URI . $element->href;
$this->items[] = $item;
}
}
$item['uri'] = self::URI . $element->href;
$this->items[] = $item;
}
}
}

View File

@@ -1,28 +1,25 @@
<?php
class ComboiosDePortugalBridge extends BridgeAbstract {
const NAME = 'CP | Avisos';
const BASE_URI = 'https://www.cp.pt';
const URI = self::BASE_URI . '/passageiros/pt';
const DESCRIPTION = 'Comboios de Portugal | Avisos';
const MAINTAINER = 'somini';
class ComboiosDePortugalBridge extends BridgeAbstract
{
const NAME = 'CP | Avisos';
const BASE_URI = 'https://www.cp.pt';
const URI = self::BASE_URI . '/passageiros/pt';
const DESCRIPTION = 'Comboios de Portugal | Avisos';
const MAINTAINER = 'somini';
public function collectData() {
# Do not verify SSL certificate (the server doesn't send the intermediate)
# https://github.com/RSS-Bridge/rss-bridge/issues/2397
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos', array(), array(
CURLOPT_SSL_VERIFYPEER => 0,
));
public function collectData()
{
# Do not verify SSL certificate (the server doesn't send the intermediate)
# https://github.com/RSS-Bridge/rss-bridge/issues/2397
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos', [], [
CURLOPT_SSL_VERIFYPEER => 0,
]);
foreach($html->find('.warnings-table a') as $element) {
$item = array();
foreach ($html->find('.warnings-table a') as $element) {
$item = [];
$item['title'] = $element->innertext;
$item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href)));
$item['title'] = $element->innertext;
$item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href)));
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
}

View File

@@ -1,71 +1,64 @@
<?php
class ComicsKingdomBridge extends BridgeAbstract {
class ComicsKingdomBridge extends BridgeAbstract
{
const MAINTAINER = 'stjohnjohnson';
const NAME = 'Comics Kingdom Unofficial RSS';
const URI = 'https://comicskingdom.com/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Comics Kingdom Unofficial RSS';
const PARAMETERS = [ [
'comicname' => [
'name' => 'comicname',
'type' => 'text',
'exampleValue' => 'mutts',
'title' => 'The name of the comic in the URL after https://comicskingdom.com/',
'required' => true
]
]];
const MAINTAINER = 'stjohnjohnson';
const NAME = 'Comics Kingdom Unofficial RSS';
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
)
));
public function collectData()
{
$html = getSimpleHTMLDOM($this->getURI(), [], [], true, false);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI(), array(), array(), true, false);
// Get author from first page
$author = $html->find('div.author p', 0);
;
// Get author from first page
$author = $html->find('div.author p', 0);;
// Get current date/link
$link = $html->find('meta[property=og:url]', -1)->content;
for ($i = 0; $i < 3; $i++) {
$item = [];
// Get current date/link
$link = $html->find('meta[property=og:url]', -1)->content;
for($i = 0; $i < 3; $i++) {
$item = array();
$page = getSimpleHTMLDOM($link);
$page = getSimpleHTMLDOM($link);
$imagelink = $page->find('meta[property=og:image]', 0)->content;
$imagelink = $page->find('meta[property=og:image]', 0)->content;
$date = explode('/', $link);
$date = explode('/', $link);
$item['id'] = $imagelink;
$item['uri'] = $link;
$item['author'] = $author;
$item['title'] = 'Comics Kingdom ' . $this->getInput('comicname');
$item['timestamp'] = DateTime::createFromFormat('Y-m-d', $date[count($date) - 1])->getTimestamp();
$item['content'] = '<img src="' . $imagelink . '" />';
$item['id'] = $imagelink;
$item['uri'] = $link;
$item['author'] = $author;
$item['title'] = 'Comics Kingdom ' . $this->getInput('comicname');
$item['timestamp'] = DateTime::createFromFormat('Y-m-d', $date[count($date) - 1])->getTimestamp();
$item['content'] = '<img src="' . $imagelink . '" />';
$this->items[] = $item;
$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
}
}
}
$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
}
}
public function getURI()
{
if (!is_null($this->getInput('comicname'))) {
return self::URI . urlencode($this->getInput('comicname'));
}
public function getURI(){
if(!is_null($this->getInput('comicname'))) {
return self::URI . urlencode($this->getInput('comicname'));
}
return parent::getURI();
}
return parent::getURI();
}
public function getName()
{
if (!is_null($this->getInput('comicname'))) {
return $this->getInput('comicname') . ' - Comics Kingdom';
}
public function getName(){
if(!is_null($this->getInput('comicname'))) {
return $this->getInput('comicname') . ' - Comics Kingdom';
}
return parent::getName();
}
return parent::getName();
}
}

View File

@@ -1,31 +1,26 @@
<?php
class CommonDreamsBridge extends FeedExpander {
class CommonDreamsBridge extends FeedExpander
{
const MAINTAINER = 'nyutag';
const NAME = 'CommonDreams Bridge';
const URI = 'https://www.commondreams.org/';
const DESCRIPTION = 'Returns the newest articles.';
const MAINTAINER = 'nyutag';
const NAME = 'CommonDreams Bridge';
const URI = 'https://www.commondreams.org/';
const DESCRIPTION = 'Returns the newest articles.';
public function collectData()
{
$this->collectExpandableDatas('http://www.commondreams.org/rss.xml', 10);
}
public function collectData(){
$this->collectExpandableDatas('http://www.commondreams.org/rss.xml', 10);
}
protected function parseItem($newsItem)
{
$item = parent::parseItem($newsItem);
$item['content'] = $this->extractContent($item['uri']);
return $item;
}
protected function parseItem($newsItem){
$item = parent::parseItem($newsItem);
$item['content'] = $this->extractContent($item['uri']);
return $item;
}
private function extractContent($url)
{
$dom = getSimpleHTMLDOMCached($url);
$summary = $dom->find('div.node__body', 0);
$text = $summary->innertext;
$dom->clear();
unset($dom);
return $text;
}
private function extractContent($url){
$html3 = getSimpleHTMLDOMCached($url);
$text = $html3->find('div[class=field--type-text-with-summary]', 0)->innertext;
$html3->clear();
unset ($html3);
return $text;
}
}

View File

@@ -1,36 +1,34 @@
<?php
class CopieDoubleBridge extends BridgeAbstract {
class CopieDoubleBridge extends BridgeAbstract
{
const MAINTAINER = 'superbaillot.net';
const NAME = 'CopieDouble';
const URI = 'http://www.copie-double.com/';
const CACHE_TIMEOUT = 14400; // 4h
const DESCRIPTION = 'CopieDouble';
const MAINTAINER = 'superbaillot.net';
const NAME = 'CopieDouble';
const URI = 'http://www.copie-double.com/';
const CACHE_TIMEOUT = 14400; // 4h
const DESCRIPTION = 'CopieDouble';
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI);
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$table = $html->find('table table', 2);
$table = $html->find('table table', 2);
foreach ($table->find('tr') as $element) {
$td = $element->find('td', 0);
foreach($table->find('tr') as $element) {
$td = $element->find('td', 0);
if ($td->class === 'couleur_1') {
$item = [];
$title = $td->innertext;
$pos = strpos($title, '<a');
$title = substr($title, 0, $pos);
$item['title'] = $title;
} elseif (strpos($element->innertext, '/images/suivant.gif') === false) {
$a = $element->find('a', 0);
$item['uri'] = self::URI . $a->href;
$content = str_replace('src="/', 'src="/' . self::URI, $element->find('td', 0)->innertext);
$content = str_replace('href="/', 'href="' . self::URI, $content);
$item['content'] = $content;
$this->items[] = $item;
}
}
}
if($td->class === 'couleur_1') {
$item = array();
$title = $td->innertext;
$pos = strpos($title, '<a');
$title = substr($title, 0, $pos);
$item['title'] = $title;
} elseif(strpos($element->innertext, '/images/suivant.gif') === false) {
$a = $element->find('a', 0);
$item['uri'] = self::URI . $a->href;
$content = str_replace('src="/', 'src="/' . self::URI, $element->find('td', 0)->innertext);
$content = str_replace('href="/', 'href="' . self::URI, $content);
$item['content'] = $content;
$this->items[] = $item;
}
}
}
}

View File

@@ -1,75 +0,0 @@
<?php
class CorreioDaFeiraBridge extends BridgeAbstract
{
const NAME = 'Correio da Feira';
const URI = 'https://www.correiodafeira.pt/';
const DESCRIPTION = 'Returns news from the Portuguese local newspaper Correio da Feira';
const MAINTAINER = 'rmscoelho';
const CACHE_TIMEOUT = 86400;
const PARAMETERS = [
[
'feed' => [
'name' => 'News Feed',
'type' => 'list',
'title' => 'Feeds from the Portuguese sports newspaper A BOLA.PT',
'values' => [
'Cultura' => 'cultura',
'Desporto' => 'desporto',
'Economia' => 'economia',
'Entrevista' => 'entrevista',
'Freguesias' => 'freguesias',
'Justiça' => 'justica',
'Opinião' => 'opiniao',
'Política' => 'politica',
'Reportagem' => 'reportagem',
'Sociedade' => 'sociedade',
'Tecnologia' => 'tecnologia',
]
]
]
];
public function getIcon()
{
return 'https://www.correiodafeira.pt/wp-content/uploads/base_reporter-200x200.jpg';
}
public function getName()
{
return !is_null($this->getKey('feed')) ? self::NAME . ' | ' . $this->getKey('feed') : self::NAME;
}
public function getURI()
{
return self::URI . $this->getInput('feed');
}
public function collectData()
{
$url = sprintf('https://www.correiodafeira.pt/categoria/%s', $this->getInput('feed'));
$dom = getSimpleHTMLDOM($url);
$dom = $dom->find('main', 0);
if (!$dom) {
throw new \Exception(sprintf('Unable to find css selector on `%s`', $url));
}
$dom = defaultLinkTo($dom, $this->getURI());
foreach ($dom->find('div.post') as $article) {
$a = $article->find('div.blog-box', 0);
//Get date and time of publishing
$time = $a->find('.post-date > :nth-child(2)', 0)->plaintext;
$datetime = explode('/', $time);
$year = $datetime[2];
$month = $datetime[1];
$day = $datetime[0];
$timestamp = mktime(0, 0, 0, $month, $day, $year);
$this->items[] = [
'title' => $a->find('h2.entry-title > a', 0)->plaintext,
'uri' => $a->find('h2.entry-title > a', 0)->href,
'author' => $a->find('li.post-author > a', 0)->plaintext,
'content' => $a->find('.entry-content > p', 0)->plaintext,
'timestamp' => $timestamp,
];
}
}
}

View File

@@ -1,29 +1,27 @@
<?php
class CourrierInternationalBridge extends FeedExpander {
class CourrierInternationalBridge extends FeedExpander
{
const MAINTAINER = 'teromene';
const NAME = 'Courrier International Bridge';
const URI = 'https://www.courrierinternational.com/';
const CACHE_TIMEOUT = 300; // 5 min
const DESCRIPTION = 'Returns the newest articles';
const MAINTAINER = 'teromene';
const NAME = 'Courrier International Bridge';
const URI = 'https://www.courrierinternational.com/';
const CACHE_TIMEOUT = 300; // 5 min
const DESCRIPTION = 'Returns the newest articles';
public function collectData()
{
$this->collectExpandableDatas(static::URI . 'feed/all/rss.xml', 20);
}
public function collectData(){
$this->collectExpandableDatas(static::URI . 'feed/all/rss.xml', 20);
}
protected function parseItem($feedItem)
{
$item = parent::parseItem($feedItem);
protected function parseItem($feedItem){
$item = parent::parseItem($feedItem);
$articlePage = getSimpleHTMLDOMCached($feedItem->link);
$content = $articlePage->find('.article-text, depeche-text', 0);
if (!$content) {
return $item;
}
$item['content'] = sanitize($content);
$articlePage = getSimpleHTMLDOMCached($feedItem->link);
$content = $articlePage->find('.article-text', 0);
if(!$content) {
$content = $articlePage->find('.depeche-text', 0);
}
return $item;
}
$item['content'] = sanitize($content);
return $item;
}
}

View File

@@ -1,112 +1,105 @@
<?php
class CraigslistBridge extends BridgeAbstract {
const MAINTAINER = 'Yaman Qalieh';
const NAME = 'Craigslist Bridge';
const URI = 'https://craigslist.org/';
const DESCRIPTION = 'Returns craigslist search results';
class CraigslistBridge extends BridgeAbstract
{
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 PARAMETERS = [ [
'region' => [
'name' => 'Region',
'title' => 'The subdomain before craigslist.org in the URL',
'exampleValue' => 'sfbay',
'required' => true
],
'search' => [
'name' => 'Search Query',
'title' => 'Everything in the URL after /search/',
'exampleValue' => 'sya?query=laptop',
'required' => true
],
'limit' => [
'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 TEST_DETECT_PARAMETERS = [
'https://sfbay.craigslist.org/search/sya?query=laptop' => [
'region' => 'sfbay', 'search' => 'sya?query=laptop'
],
'https://newyork.craigslist.org/search/sss?query=32gb+flash+drive&bundleDuplicates=1&max_price=20' => [
'region' => 'newyork', 'search' => 'sss?query=32gb+flash+drive&bundleDuplicates=1&max_price=20'
],
];
const URL_REGEX = '/^https:\/\/(?<region>\w+).craigslist.org\/search\/(?<search>.+)/';
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 detectParameters($url)
{
if (preg_match(self::URL_REGEX, $url, $matches)) {
$params = [];
$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 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);
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;
}
// 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');
// 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'));
}
// Limit the number of posts
if ($this->getInput('limit') > 0) {
$results = array_slice($results, 0, $this->getInput('limit'));
}
foreach($results as $post) {
foreach ($results as $post) {
// Skip "nearby results" banner and results
// This only appears when searchNearby is not specified
if ($post->tag == 'h4') {
break;
}
// Skip "nearby results" banner and results
if ($post->tag == 'h4') {
break;
}
$item = [];
$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;
$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 . ' '
. $post->find('.result-hood', 0)->plaintext;
$price = $post->find('.result-price', 0)->plaintext ?? '';
// Find the location (local and nearby results if searchNearby=1)
$nearby = $post->find('.result-hood, span.nearby', 0)->plaintext ?? '';
$item['content'] = sprintf('%s %s', $price, $nearby);
$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;
}
}
$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;
}
}
}

View File

@@ -1,233 +1,227 @@
<?php
class CrewbayBridge extends BridgeAbstract {
const MAINTAINER = 'couraudt';
const NAME = 'Crewbay Bridge';
const URI = 'https://www.crewbay.com';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = array(
array(
'keyword' => array(
'name' => 'Filter by keyword',
'title' => 'Enter the keyword to filter here'
),
'type' => array(
'name' => 'Type of search',
'title' => 'Choose between finding a boat or a crew',
'type' => 'list',
'values' => array(
'Find a boat' => 'boats',
'Find a crew' => 'crew'
)
),
'status' => array(
'name' => 'Status on the boat',
'title' => 'Choose between recreational or professional classified ads',
'type' => 'list',
'values' => array(
'Recreational' => 'recreational',
'Professional' => 'professional'
)
),
'recreational_position' => array(
'name' => 'Recreational position wanted',
'title' => 'Filter by recreational position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => array(
'' => '',
'Amateur Crew' => 'Amateur Crew',
'Friendship' => 'Friendship',
'Competent Crew' => 'Competent Crew',
'Racing' => 'Racing',
'Voluntary work' => 'Voluntary work',
'Mile building' => 'Mile building'
)
),
'professional_position' => array(
'name' => 'Professional position wanted',
'title' => 'Filter by professional position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => array(
'' => '',
'1st Engineer' => '1st Engineer',
'1st Mate' => '1st Mate',
'Beautician' => 'Beautician',
'Bosun' => 'Bosun',
'Captain' => 'Captain',
'Chef' => 'Chef',
'Steward(ess)' => 'Steward(ess)',
'Deckhand' => 'Deckhand',
'Delivery Crew' => 'Delivery Crew',
'Dive Instructor' => 'Dive Instructor',
'Masseur' => 'Masseur',
'Medical Staff' => 'Medical Staff',
'Nanny' => 'Nanny',
'Navigator' => 'Navigator',
'Racing Crew' => 'Racing Crew',
'Teacher' => 'Teacher',
'Electrical Engineer' => 'Electrical Engineer',
'Fitter' => 'Fitter',
'2nd Engineer' => '2nd Engineer',
'3rd Engineer' => '3rd Engineer',
'Lead Deckhand' => 'Lead Deckhand',
'Security Officer' => 'Security Officer',
'O.O.W' => 'O.O.W',
'1st Officer' => '1st Officer',
'2nd Officer' => '2nd Officer',
'3rd Officer' => '3rd Officer',
'Captain/Engineer' => 'Captain/Engineer',
'Hairdresser' => 'Hairdresser',
'Fitness Trainer' => 'Fitness Trainer',
'Laundry' => 'Laundry',
'Solo Steward/ess' => 'Solo Steward/ess',
'Stew/Deck' => 'Stew/Deck',
'2nd Steward/ess' => '2nd Steward/ess',
'3rd Steward/ess' => '3rd Steward/ess',
'Chief Steward/ess' => 'Chief Steward/ess',
'Head Housekeeper' => 'Head Housekeeper',
'Purser' => 'Purser',
'Cook' => 'Cook',
'Cook/Stew' => 'Cook/Stew',
'2nd Chef' => '2nd Chef',
'Head Chef' => 'Head Chef',
'Administrator' => 'Administrator',
'P.A' => 'P.A',
'Villa staff' => 'Villa staff',
'Housekeeping/Stew' => 'Housekeeping/Stew',
'Stew/Beautician' => 'Stew/Beautician',
'Stew/Masseuse' => 'Stew/Masseuse',
'Manager' => 'Manager',
'Sailing instructor' => 'Sailing instructor'
)
)
)
);
class CrewbayBridge extends BridgeAbstract
{
const MAINTAINER = 'couraudt';
const NAME = 'Crewbay Bridge';
const URI = 'https://www.crewbay.com';
const DESCRIPTION = 'Returns the newest sailing offers.';
const PARAMETERS = [
[
'keyword' => [
'name' => 'Filter by keyword',
'title' => 'Enter the keyword to filter here'
],
'type' => [
'name' => 'Type of search',
'title' => 'Choose between finding a boat or a crew',
'type' => 'list',
'values' => [
'Find a boat' => 'boats',
'Find a crew' => 'crew'
]
],
'status' => [
'name' => 'Status on the boat',
'title' => 'Choose between recreational or professional classified ads',
'type' => 'list',
'values' => [
'Recreational' => 'recreational',
'Professional' => 'professional'
]
],
'recreational_position' => [
'name' => 'Recreational position wanted',
'title' => 'Filter by recreational position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => [
'' => '',
'Amateur Crew' => 'Amateur Crew',
'Friendship' => 'Friendship',
'Competent Crew' => 'Competent Crew',
'Racing' => 'Racing',
'Voluntary work' => 'Voluntary work',
'Mile building' => 'Mile building'
]
],
'professional_position' => [
'name' => 'Professional position wanted',
'title' => 'Filter by professional position you wanted aboard',
'required' => false,
'type' => 'list',
'values' => [
'' => '',
'1st Engineer' => '1st Engineer',
'1st Mate' => '1st Mate',
'Beautician' => 'Beautician',
'Bosun' => 'Bosun',
'Captain' => 'Captain',
'Chef' => 'Chef',
'Steward(ess)' => 'Steward(ess)',
'Deckhand' => 'Deckhand',
'Delivery Crew' => 'Delivery Crew',
'Dive Instructor' => 'Dive Instructor',
'Masseur' => 'Masseur',
'Medical Staff' => 'Medical Staff',
'Nanny' => 'Nanny',
'Navigator' => 'Navigator',
'Racing Crew' => 'Racing Crew',
'Teacher' => 'Teacher',
'Electrical Engineer' => 'Electrical Engineer',
'Fitter' => 'Fitter',
'2nd Engineer' => '2nd Engineer',
'3rd Engineer' => '3rd Engineer',
'Lead Deckhand' => 'Lead Deckhand',
'Security Officer' => 'Security Officer',
'O.O.W' => 'O.O.W',
'1st Officer' => '1st Officer',
'2nd Officer' => '2nd Officer',
'3rd Officer' => '3rd Officer',
'Captain/Engineer' => 'Captain/Engineer',
'Hairdresser' => 'Hairdresser',
'Fitness Trainer' => 'Fitness Trainer',
'Laundry' => 'Laundry',
'Solo Steward/ess' => 'Solo Steward/ess',
'Stew/Deck' => 'Stew/Deck',
'2nd Steward/ess' => '2nd Steward/ess',
'3rd Steward/ess' => '3rd Steward/ess',
'Chief Steward/ess' => 'Chief Steward/ess',
'Head Housekeeper' => 'Head Housekeeper',
'Purser' => 'Purser',
'Cook' => 'Cook',
'Cook/Stew' => 'Cook/Stew',
'2nd Chef' => '2nd Chef',
'Head Chef' => 'Head Chef',
'Administrator' => 'Administrator',
'P.A' => 'P.A',
'Villa staff' => 'Villa staff',
'Housekeeping/Stew' => 'Housekeeping/Stew',
'Stew/Beautician' => 'Stew/Beautician',
'Stew/Masseuse' => 'Stew/Masseuse',
'Manager' => 'Manager',
'Sailing instructor' => 'Sailing instructor'
]
]
]
];
public function collectData() {
$url = $this->getURI();
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
public function collectData()
{
$url = $this->getURI();
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
$annonces = $html->find('#SearchResults div.result');
$limit = 0;
$annonces = $html->find('#SearchResults div.result');
$limit = 0;
foreach ($annonces as $annonce) {
$detail = $annonce->find('.btn--profile', 0);
$htmlDetail = getSimpleHTMLDOMCached($detail->href);
foreach ($annonces as $annonce) {
$detail = $annonce->find('.btn--profile', 0);
$htmlDetail = getSimpleHTMLDOMCached($detail->href);
if (!empty($this->getInput('recreational_position')) || !empty($this->getInput('professional_position'))) {
if ($this->getInput('type') == 'boats') {
if ($this->getInput('status') == 'professional') {
$positions = array($annonce->find('.title .position', 0)->plaintext);
} else {
$positions = array(str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext));
}
} else {
$list = $htmlDetail->find('.viewer-details .viewer-list');
$positions = explode("\r\n", end($list)->find('span.value', 0)->plaintext);
}
if (!empty($this->getInput('recreational_position')) || !empty($this->getInput('professional_position'))) {
if ($this->getInput('type') == 'boats') {
if ($this->getInput('status') == 'professional') {
$positions = [$annonce->find('.title .position', 0)->plaintext];
} else {
$positions = [str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext)];
}
} else {
$list = $htmlDetail->find('.viewer-details .viewer-list');
$positions = explode("\r\n", end($list)->find('span.value', 0)->plaintext);
}
$found = false;
$keyword = $this->getInput('status') == 'professional' ? 'professional_position' : 'recreational_position';
foreach ($positions as $position) {
if (strpos(trim($position), $this->getInput($keyword)) !== false) {
$found = true;
break;
}
}
$found = false;
$keyword = $this->getInput('status') == 'professional' ? 'professional_position' : 'recreational_position';
foreach ($positions as $position) {
if (strpos(trim($position), $this->getInput($keyword)) !== false) {
$found = true;
break;
}
}
if (!$found) {
continue;
}
}
if (!$found) {
continue;
}
}
$item = array();
$item = [];
if ($this->getInput('type') == 'boats') {
$titleSelector = '.title h2';
} else {
$titleSelector = '.layout__item h2';
}
$userName = $annonce->find('.result--description a', 0)->plaintext;
$annonceTitle = trim($annonce->find($titleSelector, 0)->plaintext);
if (empty($annonceTitle)) {
$item['title'] = $userName;
} else {
$item['title'] = $userName . ' - ' . $annonceTitle;
}
if ($this->getInput('type') == 'boats') {
$titleSelector = '.title h2';
} else {
$titleSelector = '.layout__item h2';
}
$userName = $annonce->find('.result--description a', 0)->plaintext;
$annonceTitle = trim($annonce->find($titleSelector, 0)->plaintext);
if (empty($annonceTitle)) {
$item['title'] = $userName;
} else {
$item['title'] = $userName . ' - ' . $annonceTitle;
}
$item['uri'] = $detail->href;
$images = $annonce->find('.avatar img');
$item['enclosures'] = array(end($images)->getAttribute('src'));
$item['uri'] = $detail->href;
$images = $annonce->find('.avatar img');
$item['enclosures'] = [end($images)->getAttribute('src')];
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
$sections = $htmlDetail->find('.viewer-container .viewer-section');
foreach ($sections as $section) {
if ($section->find('.viewer-section-title', 0)) {
$class = str_replace('viewer-', '', explode(' ', $section->getAttribute('class'))[0]);
if (!in_array($class, array('apply', 'photos', 'reviews', 'contact', 'experience', 'qa'))) {
// Basic sections
$content .= $section->find('.viewer-section-title h3', 0)->outertext;
$content .= $section->find('.viewer-section-content', 0)->innertext;
}
} else {
// Info section
$content .= $section->find('.viewer-section-content h3', 0)->outertext;
$content .= $section->find('.viewer-section-content p', 0)->outertext;
}
}
$sections = $htmlDetail->find('.viewer-container .viewer-section');
foreach ($sections as $section) {
if ($section->find('.viewer-section-title', 0)) {
$class = str_replace('viewer-', '', explode(' ', $section->getAttribute('class'))[0]);
if (!in_array($class, ['apply', 'photos', 'reviews', 'contact', 'experience', 'qa'])) {
// Basic sections
$content .= $section->find('.viewer-section-title h3', 0)->outertext;
$content .= $section->find('.viewer-section-content', 0)->innertext;
}
} else {
// Info section
$content .= $section->find('.viewer-section-content h3', 0)->outertext;
$content .= $section->find('.viewer-section-content p', 0)->outertext;
}
}
if (!empty($this->getInput('keyword'))) {
$keyword = strtolower($this->getInput('keyword'));
if (strpos(strtolower($item['title']), $keyword) === false) {
if (strpos(strtolower($content), $keyword) === false) {
continue;
}
}
}
if (!empty($this->getInput('keyword'))) {
$keyword = strtolower($this->getInput('keyword'));
if (strpos(strtolower($item['title']), $keyword) === false) {
if (strpos(strtolower($content), $keyword) === false) {
continue;
}
}
}
$item['content'] = $content;
$item['content'] = $content;
$tags = $htmlDetail->find('li.viewer-tags--tag');
foreach ($tags as $tag) {
if (!isset($item['categories'])) {
$item['categories'] = array();
}
$text = trim($tag->plaintext);
if (!in_array($text, $item['categories'])) {
$item['categories'][] = $text;
}
}
$tags = $htmlDetail->find('li.viewer-tags--tag');
foreach ($tags as $tag) {
if (!isset($item['categories'])) {
$item['categories'] = [];
}
$text = trim($tag->plaintext);
if (!in_array($text, $item['categories'])) {
$item['categories'][] = $text;
}
}
$this->items[] = $item;
$limit += 1;
$this->items[] = $item;
$limit += 1;
if ($limit == 10) break;
}
}
if ($limit == 10) {
break;
}
}
}
public function getURI() {
$uri = parent::getURI();
public function getURI()
{
$uri = parent::getURI();
if ($this->getInput('type') == 'boats') {
$uri .= '/boats';
} else {
$uri .= '/crew';
}
if ($this->getInput('type') == 'boats') {
$uri .= '/boats';
} else {
$uri .= '/crew';
}
if ($this->getInput('status') == 'professional') {
$uri .= '/professional';
} else {
$uri .= '/recreational';
}
if ($this->getInput('status') == 'professional') {
$uri .= '/professional';
} else {
$uri .= '/recreational';
}
return $uri;
}
return $uri;
}
}

View File

@@ -1,47 +1,44 @@
<?php
class CryptomeBridge extends BridgeAbstract {
class CryptomeBridge extends BridgeAbstract
{
const MAINTAINER = 'BoboTiG';
const NAME = 'Cryptome';
const URI = 'https://cryptome.org/';
const CACHE_TIMEOUT = 21600; // 6h
const DESCRIPTION = 'Returns the N most recent documents.';
const PARAMETERS = [ [
'n' => [
'name' => 'number of elements',
'type' => 'number',
'required' => true,
'exampleValue' => 10
]
]];
const MAINTAINER = 'BoboTiG';
const NAME = 'Cryptome';
const URI = 'https://cryptome.org/';
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',
'required' => true,
'exampleValue' => 10
)
));
public function getIcon()
{
return self::URI . '/favicon.ico';
}
public function getIcon() {
return self::URI . '/favicon.ico';
}
public function collectData()
{
$html = getSimpleHTMLDOM(self::URI);
public function collectData(){
$html = getSimpleHTMLDOM(self::URI);
$number = $this->getInput('n');
if (!empty($number)) {
$num = min($number, 20);
}
$i = 0;
foreach ($html->find('pre', 1)->find('b') as $element) {
foreach ($element->find('a') as $element1) {
$item = [];
$item['uri'] = $element1->href;
$item['title'] = $element->plaintext;
$this->items[] = $item;
$number = $this->getInput('n');
if(!empty($number)) {
$num = min($number, 20);
}
$i = 0;
foreach($html->find('pre', 1)->find('b') as $element) {
foreach($element->find('a') as $element1) {
$item = array();
$item['uri'] = $element1->href;
$item['title'] = $element->plaintext;
$this->items[] = $item;
if ($i > $num) {
break 2;
}
$i++;
}
}
}
if ($i > $num) {
break 2;
}
$i++;
}
}
}
}

View File

@@ -1,102 +0,0 @@
<?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 = [[
'gist' => [
'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 = [];
$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;
}
}

View File

@@ -1,113 +1,108 @@
<?php
class CuriousCatBridge extends BridgeAbstract {
const NAME = 'Curious Cat Bridge';
const URI = 'https://curiouscat.me';
const DESCRIPTION = 'Returns list of newest questions and answers for a user profile';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(array(
'username' => array(
'name' => 'Username',
'type' => 'text',
'required' => true,
'exampleValue' => 'koethekoethe',
)
));
class CuriousCatBridge extends BridgeAbstract
{
const NAME = 'Curious Cat Bridge';
const URI = 'https://curiouscat.me';
const DESCRIPTION = 'Returns list of newest questions and answers for a user profile';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = [[
'username' => [
'name' => 'Username',
'type' => 'text',
'required' => true,
'exampleValue' => 'koethekoethe',
]
]];
const CACHE_TIMEOUT = 3600;
const CACHE_TIMEOUT = 3600;
public function collectData() {
public function collectData()
{
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
$url = self::URI . '/api/v2/profile?username=' . urlencode($this->getInput('username'));
$apiJson = getContents($url);
$apiJson = getContents($url);
$apiData = Json::decode($apiJson);
if (isset($apiData['error'])) {
throw new \Exception($apiData['error_code']);
}
$apiData = json_decode($apiJson, true);
foreach ($apiData['posts'] as $post) {
$item = [];
foreach($apiData['posts'] as $post) {
$item = array();
$item['author'] = 'Anonymous';
$item['author'] = 'Anonymous';
if ($post['senderData']['id'] !== false) {
$item['author'] = $post['senderData']['username'];
}
if ($post['senderData']['id'] !== false) {
$item['author'] = $post['senderData']['username'];
}
$item['uri'] = $this->getURI() . '/post/' . $post['id'];
$item['title'] = $this->ellipsisTitle($post['comment']);
$item['uri'] = $this->getURI() . '/post/' . $post['id'];
$item['title'] = $this->ellipsisTitle($post['comment']);
$item['content'] = $this->processContent($post);
$item['timestamp'] = $post['timestamp'];
$item['content'] = $this->processContent($post);
$item['timestamp'] = $post['timestamp'];
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
public function getURI()
{
if (!is_null($this->getInput('username'))) {
return self::URI . '/' . $this->getInput('username');
}
public function getURI() {
return parent::getURI();
}
if (!is_null($this->getInput('username'))) {
return self::URI . '/' . $this->getInput('username');
}
public function getName()
{
if (!is_null($this->getInput('username'))) {
return $this->getInput('username') . ' - Curious Cat';
}
return parent::getURI();
}
return parent::getName();
}
public function getName() {
private function processContent($post)
{
$author = 'Anonymous';
if (!is_null($this->getInput('username'))) {
return $this->getInput('username') . ' - Curious Cat';
}
if ($post['senderData']['id'] !== false) {
$authorUrl = self::URI . '/' . $post['senderData']['username'];
return parent::getName();
}
$author = <<<EOD
private function processContent($post) {
$author = 'Anonymous';
if ($post['senderData']['id'] !== false) {
$authorUrl = self::URI . '/' . $post['senderData']['username'];
$author = <<<EOD
<a href="{$authorUrl}">{$post['senderData']['username']}</a>
EOD;
}
}
$question = $this->formatUrls($post['comment']);
$answer = $this->formatUrls($post['reply']);
$question = $this->formatUrls($post['comment']);
$answer = $this->formatUrls($post['reply']);
$content = <<<EOD
$content = <<<EOD
<p>{$author} asked:</p>
<blockquote>{$question}</blockquote><br/>
<p>{$post['addresseeData']['username']} answered:</p>
<blockquote>{$answer}</blockquote>
EOD;
return $content;
}
return $content;
}
private function ellipsisTitle($text)
{
$length = 150;
private function ellipsisTitle($text) {
$length = 150;
if (strlen($text) > $length) {
$text = explode('<br>', wordwrap($text, $length, '<br>'));
return $text[0] . '...';
}
if (strlen($text) > $length) {
$text = explode('<br>', wordwrap($text, $length, '<br>'));
return $text[0] . '...';
}
return $text;
}
return $text;
}
private function formatUrls($content)
{
return preg_replace(
'/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims',
'<a target="_blank" href="$1" target="_blank">$1</a> ',
$content
);
}
private function formatUrls($content) {
return preg_replace(
'/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims',
'<a target="_blank" href="$1" target="_blank">$1</a> ',
$content
);
}
}

View File

@@ -1,209 +1,203 @@
<?php
class DailymotionBridge extends BridgeAbstract {
class DailymotionBridge extends BridgeAbstract
{
const MAINTAINER = 'mitsukarenai';
const NAME = 'Dailymotion Bridge';
const URI = 'https://www.dailymotion.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns the 5 newest videos by username/playlist or search';
const MAINTAINER = 'mitsukarenai';
const NAME = 'Dailymotion Bridge';
const URI = 'https://www.dailymotion.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns the 5 newest videos by username/playlist or search';
const PARAMETERS = [
'By username' => [
'u' => [
'name' => 'username',
'required' => true,
'exampleValue' => 'moviepilot',
]
],
'By playlist id' => [
'p' => [
'name' => 'playlist id',
'required' => true,
'exampleValue' => 'x6xyc6',
]
],
'From search results' => [
's' => [
'name' => 'Search keyword',
'required' => true,
'exampleValue' => 'matrix',
],
'pa' => [
'name' => 'Page',
'type' => 'number',
'defaultValue' => 1,
]
]
];
const PARAMETERS = array (
'By username' => array(
'u' => array(
'name' => 'username',
'required' => true,
'exampleValue' => 'moviepilot',
)
),
'By playlist id' => array(
'p' => array(
'name' => 'playlist id',
'required' => true,
'exampleValue' => 'x6xyc6',
)
),
'From search results' => array(
's' => array(
'name' => 'Search keyword',
'required' => true,
'exampleValue' => 'matrix',
),
'pa' => array(
'name' => 'Page',
'type' => 'number',
'defaultValue' => 1,
)
)
);
private $feedName = '';
private $feedName = '';
private $apiUrl = 'https://api.dailymotion.com';
private $apiFields = 'created_time,description,id,owner.screenname,tags,thumbnail_url,title,url';
private $apiUrl = 'https://api.dailymotion.com';
private $apiFields = 'created_time,description,id,owner.screenname,tags,thumbnail_url,title,url';
public function getIcon()
{
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
}
public function getIcon() {
return 'https://static1-ssl.dmcdn.net/images/neon/favicons/android-icon-36x36.png.vf806ca4ed0deed812';
}
public function collectData()
{
if ($this->queriedContext === 'By username' || $this->queriedContext === 'By playlist id') {
$apiJson = getContents($this->getApiUrl());
public function collectData() {
$apiData = json_decode($apiJson, true);
if ($this->queriedContext === 'By username' || $this->queriedContext === 'By playlist id') {
$this->feedName = $this->getPlaylistTitle($this->getInput('p'));
$apiJson = getContents($this->getApiUrl());
foreach ($apiData['list'] as $apiItem) {
$item = [];
$apiData = json_decode($apiJson, true);
$item['uri'] = $apiItem['url'];
$item['uid'] = $apiItem['id'];
$item['title'] = $apiItem['title'];
$item['timestamp'] = $apiItem['created_time'];
$item['author'] = $apiItem['owner.screenname'];
$item['content'] = '<p><a href="' . $apiItem['url'] . '">
$this->feedName = $this->getPlaylistTitle($this->getInput('p'));
foreach ($apiData['list'] as $apiItem) {
$item = array();
$item['uri'] = $apiItem['url'];
$item['uid'] = $apiItem['id'];
$item['title'] = $apiItem['title'];
$item['timestamp'] = $apiItem['created_time'];
$item['author'] = $apiItem['owner.screenname'];
$item['content'] = '<p><a href="' . $apiItem['url'] . '">
<img src="' . $apiItem['thumbnail_url'] . '"></a></p><p>' . $apiItem['description'] . '</p>';
$item['categories'] = $apiItem['tags'];
$item['enclosures'][] = $apiItem['thumbnail_url'];
$item['categories'] = $apiItem['tags'];
$item['enclosures'][] = $apiItem['thumbnail_url'];
$this->items[] = $item;
}
}
$this->items[] = $item;
}
}
if ($this->queriedContext === 'From search results') {
$html = getSimpleHTMLDOM($this->getURI());
if ($this->queriedContext === 'From search results') {
foreach ($html->find('div.media a.preview_link') as $element) {
$item = [];
$html = getSimpleHTMLDOM($this->getURI());
$item['id'] = str_replace('/video/', '', strtok($element->href, '_'));
$metadata = $this->getMetadata($item['id']);
foreach($html->find('div.media a.preview_link') as $element) {
$item = array();
if (empty($metadata)) {
continue;
}
$item['id'] = str_replace('/video/', '', strtok($element->href, '_'));
$metadata = $this->getMetadata($item['id']);
$item['uri'] = $metadata['uri'];
$item['title'] = $metadata['title'];
$item['timestamp'] = $metadata['timestamp'];
if(empty($metadata)) {
continue;
}
$item['content'] = '<a href="'
. $item['uri']
. '"><img src="'
. $metadata['thumbnailUri']
. '" /></a><br><a href="'
. $item['uri']
. '">'
. $item['title']
. '</a>';
$item['uri'] = $metadata['uri'];
$item['title'] = $metadata['title'];
$item['timestamp'] = $metadata['timestamp'];
$this->items[] = $item;
$item['content'] = '<a href="'
. $item['uri']
. '"><img src="'
. $metadata['thumbnailUri']
. '" /></a><br><a href="'
. $item['uri']
. '">'
. $item['title']
. '</a>';
if (count($this->items) >= 5) {
break;
}
}
}
}
$this->items[] = $item;
public function getName()
{
switch ($this->queriedContext) {
case 'By username':
$specific = $this->getInput('u');
break;
case 'By playlist id':
$specific = strtok($this->getInput('p'), '_');
if (count($this->items) >= 5) {
break;
}
}
}
}
if ($this->feedName) {
$specific = $this->feedName;
}
public function getName() {
switch($this->queriedContext) {
case 'By username':
$specific = $this->getInput('u');
break;
case 'By playlist id':
$specific = strtok($this->getInput('p'), '_');
break;
case 'From search results':
$specific = $this->getInput('s');
break;
default:
return parent::getName();
}
if ($this->feedName) {
$specific = $this->feedName;
}
return $specific . ' : Dailymotion';
}
break;
case 'From search results':
$specific = $this->getInput('s');
break;
default: return parent::getName();
}
public function getURI()
{
$uri = self::URI;
switch ($this->queriedContext) {
case 'By username':
$uri .= 'user/' . urlencode($this->getInput('u'));
break;
case 'By playlist id':
$uri .= 'playlist/' . urlencode(strtok($this->getInput('p'), '_'));
break;
case 'From search results':
$uri .= 'search/' . urlencode($this->getInput('s'));
return $specific . ' : Dailymotion';
}
if (!is_null($this->getInput('pa'))) {
$pa = $this->getInput('pa');
public function getURI(){
$uri = self::URI;
switch($this->queriedContext) {
case 'By username':
$uri .= 'user/' . urlencode($this->getInput('u'));
break;
case 'By playlist id':
$uri .= 'playlist/' . urlencode(strtok($this->getInput('p'), '_'));
break;
case 'From search results':
$uri .= 'search/' . urlencode($this->getInput('s'));
if ($this->getInput('pa') < 1) {
$pa = 1;
}
if(!is_null($this->getInput('pa'))) {
$pa = $this->getInput('pa');
$uri .= '/' . $pa;
}
break;
default:
return parent::getURI();
}
return $uri;
}
if ($this->getInput('pa') < 1) {
$pa = 1;
}
private function getMetadata($id)
{
$metadata = [];
$uri .= '/' . $pa;
}
break;
default: return parent::getURI();
}
return $uri;
}
$html = getSimpleHTMLDOM(self::URI . 'video/' . $id);
private function getMetadata($id) {
$metadata = array();
if (!$html) {
return $metadata;
}
$html = getSimpleHTMLDOM(self::URI . 'video/' . $id);
$metadata['title'] = $html->find('meta[property=og:title]', 0)->getAttribute('content');
$metadata['timestamp'] = strtotime(
$html->find('meta[property=video:release_date]', 0)->getAttribute('content')
);
$metadata['thumbnailUri'] = $html->find('meta[property=og:image]', 0)->getAttribute('content');
$metadata['uri'] = $html->find('meta[property=og:url]', 0)->getAttribute('content');
return $metadata;
}
if(!$html) {
return $metadata;
}
private function getPlaylistTitle($id)
{
$title = '';
$metadata['title'] = $html->find('meta[property=og:title]', 0)->getAttribute('content');
$metadata['timestamp'] = strtotime(
$html->find('meta[property=video:release_date]', 0)->getAttribute('content')
);
$metadata['thumbnailUri'] = $html->find('meta[property=og:image]', 0)->getAttribute('content');
$metadata['uri'] = $html->find('meta[property=og:url]', 0)->getAttribute('content');
return $metadata;
}
$url = self::URI . 'playlist/' . $id;
private function getPlaylistTitle($id) {
$title = '';
$html = getSimpleHTMLDOM($url);
$url = self::URI . 'playlist/' . $id;
$title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
return $title;
}
$html = getSimpleHTMLDOM($url);
private function getApiUrl()
{
switch ($this->queriedContext) {
case 'By username':
return $this->apiUrl . '/user/' . $this->getInput('u')
. '/videos?fields=' . urlencode($this->apiFields) . '&availability=1&sort=recent&limit=5';
break;
case 'By playlist id':
return $this->apiUrl . '/playlist/' . $this->getInput('p')
. '/videos?fields=' . urlencode($this->apiFields) . '&limit=5';
break;
}
}
$title = $html->find('meta[property=og:title]', 0)->getAttribute('content');
return $title;
}
private function getApiUrl() {
switch($this->queriedContext) {
case 'By username':
return $this->apiUrl . '/user/' . $this->getInput('u')
. '/videos?fields=' . urlencode($this->apiFields) . '&availability=1&sort=recent&limit=5';
break;
case 'By playlist id':
return $this->apiUrl . '/playlist/' . $this->getInput('p')
. '/videos?fields=' . urlencode($this->apiFields) . '&limit=5';
break;
}
}
}

Some files were not shown because too many files have changed in this diff Show More