mirror of
https://github.com/RSS-Bridge/rss-bridge.git
synced 2025-08-18 14:22:38 +02:00
Compare commits
1 Commits
2023-09-24
...
fix2
Author | SHA1 | Date | |
---|---|---|---|
|
52ccf649cd |
@@ -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
|
@@ -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"
|
||||
}
|
@@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@@ -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 unix:/var/run/php/php8.2-fpm.sock;
|
||||
}
|
||||
}
|
@@ -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'
|
@@ -1,4 +0,0 @@
|
||||
# Reformat code base to PSR12
|
||||
4f75591060d95208a301bc6bf460d875631b29cc
|
||||
# Fix coding style missed by phpbcf
|
||||
951092eef374db048b77bac85e75e3547bfac702
|
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -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
|
||||
|
46
.github/CONTRIBUTING.md
vendored
46
.github/CONTRIBUTING.md
vendored
@@ -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)
|
||||
|
2
.github/ISSUE_TEMPLATE/bridge-request.md
vendored
2
.github/ISSUE_TEMPLATE/bridge-request.md
vendored
@@ -60,5 +60,5 @@ Please describe what you expect from the bridge. Whenever possible provide sampl
|
||||
|
||||
Keep in mind that opening a request does not guarantee the bridge being implemented! That depends entirely on the interest and time of others to make the bridge for you.
|
||||
|
||||
You can also implement your own bridge (with support of the community if needed). Find more information in the [RSS-Bridge 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.
|
||||
-->
|
||||
|
24
.github/prtester.py
vendored
24
.github/prtester.py
vendored
@@ -1,5 +1,4 @@
|
||||
import requests
|
||||
import itertools
|
||||
from bs4 import BeautifulSoup
|
||||
from datetime import datetime
|
||||
import os.path
|
||||
@@ -16,7 +15,6 @@ def testBridges(bridges,status):
|
||||
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
|
||||
@@ -49,30 +47,20 @@ def testBridges(bridges,status):
|
||||
if parameter.get('type') == 'checkbox':
|
||||
if parameter.has_attr('checked'):
|
||||
formstring = formstring + '&' + parameter.get('name') + '=on'
|
||||
for listing in lists:
|
||||
for list 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:
|
||||
for selectionentry in list.contents:
|
||||
if 'selected' in selectionentry.attrs:
|
||||
selectionvalue = selectionentry.get('value')
|
||||
break
|
||||
formstring = formstring + '&' + listname + '=' + selectionvalue
|
||||
if selectionvalue == '':
|
||||
selectionvalue = list.contents[0].get('value')
|
||||
formstring = formstring + '&' + list.get('name') + '=' + 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 = r.text.replace('static/HtmlFormat.css','https://feed.eugenemolotov.ru/static/HtmlFormat.css')
|
||||
pagetext = pagetext.encode("utf_8")
|
||||
termpad = requests.post(url="https://termpad.com/", data=pagetext)
|
||||
termpadurl = termpad.text
|
||||
|
14
.github/workflows/dockerbuild.yml
vendored
14
.github/workflows/dockerbuild.yml
vendored
@@ -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
|
||||
|
6
.github/workflows/documentation.yml
vendored
6
.github/workflows/documentation.yml
vendored
@@ -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
|
11
.github/workflows/lint.yml
vendored
11
.github/workflows/lint.yml
vendored
@@ -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
|
||||
|
16
.github/workflows/prhtmlgenerator.yml
vendored
16
.github/workflows/prhtmlgenerator.yml
vendored
@@ -11,18 +11,18 @@ jobs:
|
||||
# Needs additional permissions https://github.com/actions/first-interaction/issues/10#issuecomment-1041402989
|
||||
steps:
|
||||
- name: Check out self
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v2.3.2
|
||||
with:
|
||||
ref: ${{github.event.pull_request.head.ref}}
|
||||
repository: ${{github.event.pull_request.head.repo.full_name}}
|
||||
- name: Check out rss-bridge
|
||||
run: |
|
||||
PR=${{github.event.number}};
|
||||
wget -O requirements.txt https://raw.githubusercontent.com/$GITHUB_REPOSITORY/${{ github.event.pull_request.base.ref }}/.github/prtester-requirements.txt;
|
||||
wget https://raw.githubusercontent.com/$GITHUB_REPOSITORY/${{ github.event.pull_request.base.ref }}/.github/prtester.py;
|
||||
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 "\bbridges/.*Bridge\.php\b" | sed "s=.*\bbridges/\(.*\)Bridge\.php\b.*=\1=g" | sort | uniq > whitelist.txt
|
||||
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
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
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
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: '3.7'
|
||||
cache: 'pip'
|
||||
@@ -48,7 +48,8 @@ jobs:
|
||||
body="${body//'%'/'%25'}";
|
||||
body="${body//$'\n'/'%0A'}";
|
||||
body="${body//$'\r'/'%0D'}";
|
||||
echo "bodylength=${#body}" >> $GITHUB_OUTPUT
|
||||
echo "::set-output name=bodylength::${#body}"
|
||||
echo "::set-output name=body::$body"
|
||||
- name: Find Comment
|
||||
if: ${{ steps.testrun.outputs.bodylength > 130 }}
|
||||
uses: peter-evans/find-comment@v2
|
||||
@@ -63,5 +64,6 @@ jobs:
|
||||
with:
|
||||
comment-id: ${{ steps.fc.outputs.comment-id }}
|
||||
issue-number: ${{ github.event.pull_request.number }}
|
||||
body-file: comment.txt
|
||||
body: |
|
||||
${{ steps.testrun.outputs.body }}
|
||||
edit-mode: replace
|
21
.github/workflows/tests.yml
vendored
21
.github/workflows/tests.yml
vendored
@@ -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/
|
||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -229,10 +229,6 @@ pip-log.txt
|
||||
/whitelist.txt
|
||||
DEBUG
|
||||
config.ini.php
|
||||
config/*
|
||||
!config/nginx.conf
|
||||
!config/php-fpm.conf
|
||||
!config/php.ini
|
||||
|
||||
######################
|
||||
## VisualStudioCode ##
|
||||
|
223
CONTRIBUTORS.md
223
CONTRIBUTORS.md
@@ -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)
|
53
Dockerfile
53
Dockerfile
@@ -1,47 +1,24 @@
|
||||
FROM lwthiker/curl-impersonate:0.5-ff-slim-buster AS curlimpersonate
|
||||
|
||||
FROM debian:12-slim 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"
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update && \
|
||||
apt-get install --yes --no-install-recommends \
|
||||
ca-certificates \
|
||||
nginx \
|
||||
nss-plugin-pem \
|
||||
php-curl \
|
||||
php-fpm \
|
||||
php-intl \
|
||||
# php-json is enabled by default with PHP 8.2 in Debian 12
|
||||
php-mbstring \
|
||||
php-memcached \
|
||||
# php-opcache is enabled by default with PHP 8.2 in Debian 12
|
||||
# php-openssl is enabled by default with PHP 8.2 in Debian 12
|
||||
php-sqlite3 \
|
||||
php-xml \
|
||||
php-zip \
|
||||
# php-zlib is enabled by default with PHP 8.2 in Debian 12
|
||||
&& \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
ENV APACHE_DOCUMENT_ROOT=/app
|
||||
|
||||
# logs should go to stdout / stderr
|
||||
RUN ln -sfT /dev/stderr /var/log/nginx/error.log; \
|
||||
ln -sfT /dev/stdout /var/log/nginx/access.log; \
|
||||
chown -R --no-dereference www-data:adm /var/log/nginx/
|
||||
|
||||
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
|
||||
|
||||
COPY ./config/nginx.conf /etc/nginx/sites-available/default
|
||||
COPY ./config/php-fpm.conf /etc/php/8.2/fpm/pool.d/rss-bridge.conf
|
||||
COPY ./config/php.ini /etc/php/8.2/fpm/conf.d/90-rss-bridge.conf
|
||||
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/
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
ENTRYPOINT ["/app/docker-entrypoint.sh"]
|
||||
CMD ["/app/docker-entrypoint.sh"]
|
||||
|
651
README.md
651
README.md
@@ -1,361 +1,336 @@
|
||||
# RSS-Bridge
|
||||
|
||||

|
||||
|
||||
RSS-Bridge is a web application.
|
||||
|
||||
It generates web feeds for websites that don't have one.
|
||||
|
||||
Officially hosted instance: https://rss-bridge.org/bridge01/
|
||||
|
||||
===
|
||||
[](UNLICENSE)
|
||||
[](https://github.com/rss-bridge/rss-bridge/releases/latest)
|
||||
[](https://web.libera.chat/#rssbridge)
|
||||
[](https://matrix.to/#/#rssbridge:libera.chat)
|
||||
[](https://github.com/RSS-Bridge/rss-bridge/actions)
|
||||
|
||||
|||
|
||||
|:-:|:-:|
|
||||
|||
|
||||
|||
|
||||
|||
|
||||
|||
|
||||
|
||||
## A subset of bridges (17/412)
|
||||
|
||||
* `CssSelectorBridge`: [Scrape out a feed using CSS selectors](https://rss-bridge.org/bridge01/#bridge-CssSelectorBridge)
|
||||
* `FeedMergeBridge`: [Combine multiple feeds into one](https://rss-bridge.org/bridge01/#bridge-FeedMergeBridge)
|
||||
* `FeedReducerBridge`: [Reduce a noisy feed by some percentage](https://rss-bridge.org/bridge01/#bridge-FeedReducerBridge)
|
||||
* `FilterBridge`: [Filter a feed by excluding/including items by keyword](https://rss-bridge.org/bridge01/#bridge-FilterBridge)
|
||||
* `GettrBridge`: [Fetches the latest posts from a GETTR user](https://rss-bridge.org/bridge01/#bridge-GettrBridge)
|
||||
* `MastodonBridge`: [Fetches statuses from a Mastodon (ActivityPub) instance](https://rss-bridge.org/bridge01/#bridge-MastodonBridge)
|
||||
* `RedditBridge`: [Fetches posts from a user/subredit (with filtering options)](https://rss-bridge.org/bridge01/#bridge-RedditBridge)
|
||||
* `RumbleBridge`: [Fetches channel/user videos](https://rss-bridge.org/bridge01/#bridge-RumbleBridge)
|
||||
* `SoundcloudBridge`: [Fetches music by username](https://rss-bridge.org/bridge01/#bridge-SoundcloudBridge)
|
||||
* `TelegramBridge`: [Fetches posts from a public channel](https://rss-bridge.org/bridge01/#bridge-TelegramBridge)
|
||||
* `ThePirateBayBridge:` [Fetches torrents by search/user/category](https://rss-bridge.org/bridge01/#bridge-ThePirateBayBridge)
|
||||
* `TikTokBridge`: [Fetches posts by username](https://rss-bridge.org/bridge01/#bridge-TikTokBridge)
|
||||
* `TwitchBridge`: [Fetches videos from channel](https://rss-bridge.org/bridge01/#bridge-TwitchBridge)
|
||||
* `TwitterBridge`: [Fetches tweets](https://rss-bridge.org/bridge01/#bridge-TwitterBridge)
|
||||
* `VkBridge`: [Fetches posts from user/group](https://rss-bridge.org/bridge01/#bridge-VkBridge)
|
||||
* `XPathBridge`: [Scrape out a feed using XPath expressions](https://rss-bridge.org/bridge01/#bridge-XPathBridge)
|
||||
* `YoutubeBridge`: [Fetches videos by username/channel/playlist/search](https://rss-bridge.org/bridge01/#bridge-YoutubeBridge)
|
||||
* `YouTubeCommunityTabBridge`: [Fetches posts from a channel's community tab](https://rss-bridge.org/bridge01/#bridge-YouTubeCommunityTabBridge)
|
||||
|
||||
[Full documentation](https://rss-bridge.github.io/rss-bridge/index.html)
|
||||
|
||||
Check out RSS-Bridge right now on https://rss-bridge.org/bridge01/
|
||||
|
||||
Alternatively find another
|
||||
[public instance](https://rss-bridge.github.io/rss-bridge/General/Public_Hosts.html).
|
||||
|
||||
## Tutorial
|
||||
|
||||
### Install with composer or git
|
||||
|
||||
Requires minimum PHP 7.4.
|
||||
|
||||
```shell
|
||||
apt install nginx php-fpm php-mbstring php-simplexml php-curl
|
||||
```
|
||||
|
||||
```shell
|
||||
cd /var/www
|
||||
composer create-project -v --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 from Docker Hub:
|
||||
|
||||
Install by downloading the 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 from Dockerfile
|
||||
|
||||
```bash
|
||||
# Build image from Dockerfile
|
||||
docker build -t rss-bridge .
|
||||
|
||||
# Create container
|
||||
docker create --name rss-bridge --publish 3000:80 rss-bridge
|
||||
|
||||
# Start 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/
|
||||
|
||||
### Other installation methods
|
||||
|
||||
[](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
|
||||
[](https://heroku.com/deploy)
|
||||
[](https://www.cloudron.io/store/com.rssbridgeapp.cloudronapp.html)
|
||||
|
||||
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
|
||||
|
||||
Modify `config.ini.php`:
|
||||
|
||||
enabled_bridges[] = *
|
||||
|
||||
### How to enable some bridges
|
||||
|
||||
```
|
||||
enabled_bridges[] = TwitchBridge
|
||||
enabled_bridges[] = GettrBridge
|
||||
```
|
||||
|
||||
### How to enable debug mode
|
||||
|
||||
The
|
||||
[debug mode](https://rss-bridge.github.io/rss-bridge/For_Developers/Debug_mode.html)
|
||||
disables the majority of caching operations.
|
||||
|
||||
enable_debug_mode = true
|
||||
|
||||
### How to switch to memcached as cache backend
|
||||
|
||||
```
|
||||
[cache]
|
||||
|
||||
; Cache backend: file (default), sqlite, memcached, null
|
||||
type = "memcached"
|
||||
```
|
||||
|
||||
### How to switch to sqlite3 as cache backend
|
||||
|
||||
type = "sqlite"
|
||||
|
||||
### How to disable bridge errors (as feed items)
|
||||
|
||||
When a bridge fails, RSS-Bridge will produce a feed with a single item describing the error.
|
||||
|
||||
This way, feed readers pick it up and you are notified.
|
||||
|
||||
If you don't want this behaviour, switch the error output to `http`:
|
||||
|
||||
[error]
|
||||
|
||||
; Defines how error messages are returned by RSS-Bridge
|
||||
;
|
||||
; "feed" = As part of the feed (default)
|
||||
; "http" = As HTTP error message
|
||||
; "none" = No errors are reported
|
||||
output = "http"
|
||||
|
||||
### How to accumulate errors before finally reporting it
|
||||
|
||||
Modify `report_limit` so that an error must occur 3 times before it is reported.
|
||||
|
||||
; Defines how often an error must occur before it is reported to the user
|
||||
report_limit = 3
|
||||
|
||||
### How to password-protect the instance
|
||||
|
||||
HTTP basic access authentication:
|
||||
|
||||
[authentication]
|
||||
|
||||
enable = true
|
||||
username = "alice"
|
||||
password = "cat"
|
||||
|
||||
Will typically require feed readers to be configured with the credentials.
|
||||
|
||||
It may also be possible to manually include the credentials in the URL:
|
||||
|
||||
https://alice:cat@rss-bridge.org/bridge01/?action=display&bridge=FabriceBellardBridge&format=Html
|
||||
|
||||
### How to create a new output format
|
||||
|
||||
[Create a new format](https://rss-bridge.github.io/rss-bridge/Format_API/index.html).
|
||||
|
||||
### How to run unit tests and linter
|
||||
|
||||
These commands require that you have installed the dev dependencies in `composer.json`.
|
||||
|
||||
./vendor/bin/phpunit
|
||||
./vendor/bin/phpcs --standard=phpcs.xml --warning-severity=0 --extensions=php -p ./
|
||||
|
||||
### How to spawn a minimal development environment
|
||||
|
||||
php -S 127.0.0.1:9001
|
||||
|
||||
http://127.0.0.1:9001/
|
||||
|
||||
## 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!
|
||||
|
||||
Current maintainers (as of 2023): @dvikan and @Mynacol #2519
|
||||
|
||||
## Reference
|
||||
|
||||
### Feed item structure
|
||||
|
||||
This is the feed item structure that bridges are expected to produce.
|
||||
|
||||
```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
|
||||
[](https://github.com/RSS-Bridge/rss-bridge/actions)
|
||||
[](https://hub.docker.com/r/rssbridge/rss-bridge/)
|
||||
|
||||
RSS-Bridge is a PHP project capable of generating 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.
|
||||
|
||||
**Important**: RSS-Bridge is __not__ a feed reader or feed aggregator, but a tool to generate feeds that are consumed by feed readers and feed aggregators. Find a list of feed aggregators on [Wikipedia](https://en.wikipedia.org/wiki/Comparison_of_feed_aggregators).
|
||||
|
||||
Supported sites/pages (examples)
|
||||
===
|
||||
|
||||
* `Bandcamp` : Returns last release from [bandcamp](https://bandcamp.com/) for a tag
|
||||
* `Cryptome` : Returns the most recent documents from [Cryptome.org](http://cryptome.org/)
|
||||
* `DansTonChat`: Most recent quotes from [danstonchat.com](http://danstonchat.com/)
|
||||
* `DuckDuckGo`: Most recent results from [DuckDuckGo.com](https://duckduckgo.com/)
|
||||
* `Facebook` : Returns the latest posts on a page or profile on [Facebook](https://facebook.com/) (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 (It is recommended to [configure](https://rss-bridge.github.io/rss-bridge/Bridge_Specific/Instagram.html) this bridge to work)
|
||||
* `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!
|
||||
|
||||
Output format
|
||||
===
|
||||
|
||||
RSS-Bridge is capable of producing several output formats:
|
||||
|
||||
* `Atom` : Atom feed, for use in feed readers
|
||||
* `Html` : Simple HTML page
|
||||
* `Json` : JSON, for consumption by other applications
|
||||
* `Mrss` : MRSS feed, for use in feed readers
|
||||
* `Plaintext` : Raw text, for consumption by other applications
|
||||
* `Sfeed`: Text, TAB separated
|
||||
|
||||
### Cache backends
|
||||
You can extend RSS-Bridge with your own format, using the [Format API](https://github.com/RSS-Bridge/rss-bridge/wiki/Format-API)!
|
||||
|
||||
* `File`
|
||||
* `SQLite`
|
||||
* `Memcached`
|
||||
* `Array`
|
||||
* `Null`
|
||||
Screenshot
|
||||
===
|
||||
|
||||
### Licenses
|
||||
Welcome screen:
|
||||
|
||||

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

|
||||
|
||||
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!
|
||||
*Note: External providers' applications are packaged by 3rd parties. Use at your own discretion.*
|
||||
|
||||
[](https://my.scalingo.com/deploy?source=https://github.com/sebsauvage/rss-bridge)
|
||||
[](https://heroku.com/deploy)
|
||||
[](https://www.cloudron.io/store/com.rssbridgeapp.cloudronapp.html)
|
||||
|
||||
Getting involved
|
||||
===
|
||||
|
||||
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.*
|
||||
|
||||
|
@@ -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.
|
||||
@@ -22,57 +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();
|
||||
}
|
||||
|
||||
public function execute(array $request)
|
||||
{
|
||||
if(!Debug::isEnabled()) {
|
||||
return new Response('This action is only available in debug mode!', 403);
|
||||
returnError('This action is only available in debug mode!');
|
||||
}
|
||||
|
||||
$bridgeName = $request['bridge'] ?? null;
|
||||
if (!$bridgeName) {
|
||||
return render_template('connectivity.html.php');
|
||||
}
|
||||
$bridgeClassName = $this->bridgeFactory->createBridgeClassName($bridgeName);
|
||||
if (!$bridgeClassName) {
|
||||
return new Response('Bridge not found', 404);
|
||||
}
|
||||
return $this->reportBridgeConnectivity($bridgeClassName);
|
||||
if(!isset($this->userData['bridge'])) {
|
||||
$this->returnEntryPage();
|
||||
return;
|
||||
}
|
||||
|
||||
private function reportBridgeConnectivity($bridgeClassName)
|
||||
{
|
||||
if (!$this->bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
throw new \Exception('Bridge is not whitelisted!');
|
||||
$bridgeName = $this->userData['bridge'];
|
||||
|
||||
$this->reportBridgeConnectivity($bridgeName);
|
||||
|
||||
}
|
||||
|
||||
$bridge = $this->bridgeFactory->create($bridgeClassName);
|
||||
$curl_opts = [
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
CURLOPT_FOLLOWLOCATION => true,
|
||||
];
|
||||
$result = [
|
||||
'bridge' => $bridgeClassName,
|
||||
/**
|
||||
* Generates a report about the bridge connectivity status and sends it back
|
||||
* to the user.
|
||||
*
|
||||
* The report is generated as Json-formatted string in the format
|
||||
* {
|
||||
* "bridge": "<bridge-name>",
|
||||
* "successful": true/false
|
||||
* }
|
||||
*
|
||||
* @param string $bridgeName Name of the bridge to generate the report for
|
||||
* @return void
|
||||
*/
|
||||
private function reportBridgeConnectivity($bridgeName) {
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
if(!$bridgeFac->isWhitelisted($bridgeName)) {
|
||||
header('Content-Type: text/html');
|
||||
returnServerError('Bridge is not whitelisted!');
|
||||
}
|
||||
|
||||
header('Content-Type: text/json');
|
||||
|
||||
$retVal = array(
|
||||
'bridge' => $bridgeName,
|
||||
'successful' => false,
|
||||
'http_code' => null,
|
||||
];
|
||||
try {
|
||||
$response = getContents($bridge::URI, [], $curl_opts, true);
|
||||
$result['http_code'] = $response['code'];
|
||||
if (in_array($response['code'], [200])) {
|
||||
$result['successful'] = true;
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
'http_code' => 200,
|
||||
);
|
||||
|
||||
$bridge = $bridgeFac->create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
echo json_encode($retVal);
|
||||
return;
|
||||
}
|
||||
|
||||
return new Response(Json::encode($result), 200, ['content-type' => 'text/json']);
|
||||
$curl_opts = array(
|
||||
CURLOPT_CONNECTTIMEOUT => 5
|
||||
);
|
||||
|
||||
try {
|
||||
$reply = getContents($bridge::URI, array(), $curl_opts, true);
|
||||
|
||||
if($reply) {
|
||||
$retVal['successful'] = true;
|
||||
if (isset($reply['header'])) {
|
||||
if (strpos($reply['header'], 'HTTP/1.1 301 Moved Permanently') !== false) {
|
||||
$retVal['http_code'] = 301;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(Exception $e) {
|
||||
$retVal['successful'] = false;
|
||||
}
|
||||
|
||||
echo json_encode($retVal);
|
||||
|
||||
}
|
||||
|
||||
private function returnEntryPage() {
|
||||
echo <<<EOD
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet" href="static/bootstrap.min.css">
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://use.fontawesome.com/releases/v5.6.3/css/all.css"
|
||||
integrity="sha384-UHRtZLI+pbxtHCWp1t77Bi1L4ZtiqrqD80Kn4Z8NTSRyMA2Fd33n5dQ8lWUE00s/"
|
||||
crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="static/connectivity.css">
|
||||
<script src="static/connectivity.js" type="text/javascript"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="main-content" class="container">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100"></div>
|
||||
</div>
|
||||
<div id="status-message" class="sticky-top alert alert-primary alert-dismissible fade show" role="alert">
|
||||
<i id="status-icon" class="fas fa-sync"></i>
|
||||
<span>...</span>
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close" onclick="stopConnectivityChecks()">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
<input type="text" class="form-control" id="search" onkeyup="search()" placeholder="Search for bridge..">
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
EOD;
|
||||
}
|
||||
}
|
||||
|
@@ -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.
|
||||
@@ -12,28 +11,28 @@
|
||||
* @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)) {
|
||||
foreach($bridgeFac->getBridgeNames() as $bridgeName) {
|
||||
|
||||
if(!$bridgeFac->isWhitelisted($bridgeName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
$bridge = $bridgeFac->create($bridgeName);
|
||||
|
||||
if($bridge === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams = $bridge->detectParameters($targetURL);
|
||||
|
||||
@@ -41,13 +40,14 @@ class DetectAction implements ActionInterface
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridgeParams['bridge'] = $bridgeClassName;
|
||||
$bridgeParams['bridge'] = $bridgeName;
|
||||
$bridgeParams['format'] = $format;
|
||||
|
||||
$url = '?action=display&' . http_build_query($bridgeParams);
|
||||
return new Response('', 301, ['location' => $url]);
|
||||
header('Location: ?action=display&' . http_build_query($bridgeParams), true, 301);
|
||||
die();
|
||||
|
||||
}
|
||||
|
||||
throw new \Exception('No bridge found for given URL: ' . $targetURL);
|
||||
returnClientError('No bridge found for given URL: ' . $targetURL);
|
||||
}
|
||||
}
|
||||
|
@@ -1,233 +1,260 @@
|
||||
<?php
|
||||
/**
|
||||
* This file is part of RSS-Bridge, a PHP project capable of generating RSS and
|
||||
* Atom feeds for websites that don't have one.
|
||||
*
|
||||
* For the full license information, please view the UNLICENSE file distributed
|
||||
* with this source code.
|
||||
*
|
||||
* @package Core
|
||||
* @license http://unlicense.org/ UNLICENSE
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
class DisplayAction implements ActionInterface
|
||||
{
|
||||
private CacheInterface $cache;
|
||||
private Logger $logger;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->cache = RssBridge::getCache();
|
||||
$this->logger = RssBridge::getLogger();
|
||||
class DisplayAction extends ActionAbstract {
|
||||
private function get_return_code($error) {
|
||||
$returnCode = $error->getCode();
|
||||
if ($returnCode === 301 || $returnCode === 302) {
|
||||
# Don't pass redirect codes to the exterior
|
||||
$returnCode = 508;
|
||||
}
|
||||
return $returnCode;
|
||||
}
|
||||
|
||||
public function execute(array $request)
|
||||
{
|
||||
if (Configuration::getConfig('system', 'enable_maintenance_mode')) {
|
||||
return new Response(render(__DIR__ . '/../templates/error.html.php', [
|
||||
'title' => '503 Service Unavailable',
|
||||
'message' => 'RSS-Bridge is down for maintenance.',
|
||||
]), 503);
|
||||
}
|
||||
$cacheKey = 'http_' . json_encode($request);
|
||||
/** @var Response $cachedResponse */
|
||||
$cachedResponse = $this->cache->get($cacheKey);
|
||||
if ($cachedResponse) {
|
||||
$ifModifiedSince = $_SERVER['HTTP_IF_MODIFIED_SINCE'] ?? null;
|
||||
$lastModified = $cachedResponse->getHeader('last-modified');
|
||||
if ($ifModifiedSince && $lastModified) {
|
||||
$lastModified = new \DateTimeImmutable($lastModified);
|
||||
$lastModifiedTimestamp = $lastModified->getTimestamp();
|
||||
$modifiedSince = strtotime($ifModifiedSince);
|
||||
if ($lastModifiedTimestamp <= $modifiedSince) {
|
||||
$modificationTimeGMT = gmdate('D, d M Y H:i:s ', $lastModifiedTimestamp);
|
||||
return new Response('', 304, ['last-modified' => $modificationTimeGMT . 'GMT']);
|
||||
}
|
||||
}
|
||||
return $cachedResponse;
|
||||
public function execute() {
|
||||
$bridge = array_key_exists('bridge', $this->userData) ? $this->userData['bridge'] : null;
|
||||
|
||||
$format = $this->userData['format']
|
||||
or returnClientError('You must specify a format!');
|
||||
|
||||
$bridgeFac = new \BridgeFactory();
|
||||
$bridgeFac->setWorkingDir(PATH_LIB_BRIDGES);
|
||||
|
||||
// whitelist control
|
||||
if(!$bridgeFac->isWhitelisted($bridge)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
die;
|
||||
}
|
||||
|
||||
$bridgeName = $request['bridge'] ?? null;
|
||||
if (!$bridgeName) {
|
||||
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'Missing bridge parameter']), 400);
|
||||
}
|
||||
$bridgeFactory = new BridgeFactory();
|
||||
$bridgeClassName = $bridgeFactory->createBridgeClassName($bridgeName);
|
||||
if (!$bridgeClassName) {
|
||||
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'Bridge not found']), 404);
|
||||
}
|
||||
$format = $request['format'] ?? null;
|
||||
if (!$format) {
|
||||
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'You must specify a format']), 400);
|
||||
}
|
||||
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
return new Response(render(__DIR__ . '/../templates/error.html.php', ['message' => 'This bridge is not whitelisted']), 400);
|
||||
}
|
||||
// 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()
|
||||
$noproxy = array_key_exists('_noproxy', $this->userData)
|
||||
&& filter_var($this->userData['_noproxy'], FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
if(defined('PROXY_URL') && PROXY_BYBRIDGE && $noproxy) {
|
||||
define('NOPROXY', true);
|
||||
}
|
||||
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
$formatFactory = new FormatFactory();
|
||||
$format = $formatFactory->create($format);
|
||||
// Cache timeout
|
||||
$cache_timeout = -1;
|
||||
if(array_key_exists('_cache_timeout', $this->userData)) {
|
||||
|
||||
$response = $this->createResponse($request, $bridge, $format);
|
||||
if(!CUSTOM_CACHE_TIMEOUT) {
|
||||
unset($this->userData['_cache_timeout']);
|
||||
$uri = parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH) . '?' . http_build_query($this->userData);
|
||||
header('Location: ' . $uri, true, 301);
|
||||
die();
|
||||
}
|
||||
|
||||
$cache_timeout = filter_var($this->userData['_cache_timeout'], FILTER_VALIDATE_INT);
|
||||
|
||||
if ($response->getCode() === 200) {
|
||||
$ttl = $request['_cache_timeout'] ?? null;
|
||||
if (Configuration::getConfig('cache', 'custom_timeout') && $ttl) {
|
||||
$ttl = (int) $ttl;
|
||||
} else {
|
||||
$ttl = $bridge->getCacheTimeout();
|
||||
}
|
||||
$this->cache->set($cacheKey, $response, $ttl);
|
||||
$cache_timeout = $bridge->getCacheTimeout();
|
||||
}
|
||||
|
||||
if (in_array($response->getCode(), [429, 503])) {
|
||||
$this->cache->set($cacheKey, $response, 60 * 15 + rand(1, 60 * 10)); // average 20m
|
||||
// Remove parameters that don't concern bridges
|
||||
$bridge_params = array_diff_key(
|
||||
$this->userData,
|
||||
array_fill_keys(
|
||||
array(
|
||||
'action',
|
||||
'bridge',
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
// Remove parameters that don't concern caches
|
||||
$cache_params = array_diff_key(
|
||||
$this->userData,
|
||||
array_fill_keys(
|
||||
array(
|
||||
'action',
|
||||
'format',
|
||||
'_noproxy',
|
||||
'_cache_timeout',
|
||||
'_error_time'
|
||||
), '')
|
||||
);
|
||||
|
||||
// Initialize cache
|
||||
$cacheFac = new CacheFactory();
|
||||
$cacheFac->setWorkingDir(PATH_LIB_CACHES);
|
||||
$cache = $cacheFac->create(Configuration::getConfig('cache', 'type'));
|
||||
$cache->setScope('');
|
||||
$cache->purgeCache(86400); // 24 hours
|
||||
$cache->setKey($cache_params);
|
||||
|
||||
$items = array();
|
||||
$infos = array();
|
||||
$mtime = $cache->getTime();
|
||||
|
||||
if($mtime !== false
|
||||
&& (time() - $cache_timeout < $mtime)
|
||||
&& !Debug::isEnabled()) { // Load cached data
|
||||
|
||||
// Send "Not Modified" response if client supports it
|
||||
// Implementation based on https://stackoverflow.com/a/10847262
|
||||
if(isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||
$stime = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
|
||||
|
||||
if($mtime <= $stime) { // Cached data is older or same
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s ', $mtime) . 'GMT', true, 304);
|
||||
die();
|
||||
}
|
||||
}
|
||||
|
||||
if ($response->getCode() === 500) {
|
||||
$this->cache->set($cacheKey, $response, 60 * 15);
|
||||
}
|
||||
if (rand(1, 100) === 2) {
|
||||
$this->cache->prune();
|
||||
}
|
||||
return $response;
|
||||
$cached = $cache->loadData();
|
||||
|
||||
if(isset($cached['items']) && isset($cached['extraInfos'])) {
|
||||
foreach($cached['items'] as $item) {
|
||||
$items[] = new \FeedItem($item);
|
||||
}
|
||||
|
||||
private function createResponse(array $request, BridgeAbstract $bridge, FormatInterface $format)
|
||||
{
|
||||
$items = [];
|
||||
$infos = [];
|
||||
$infos = $cached['extraInfos'];
|
||||
}
|
||||
|
||||
} else { // Collect new data
|
||||
|
||||
try {
|
||||
$bridge->loadConfiguration();
|
||||
// Remove parameters that don't concern bridges
|
||||
$bridgeData = array_diff_key($request, array_fill_keys(['action', 'bridge', 'format', '_noproxy', '_cache_timeout', '_error_time'], ''));
|
||||
$bridge->setDatas($bridgeData);
|
||||
$bridge->setDatas($bridge_params);
|
||||
$bridge->collectData();
|
||||
|
||||
$items = $bridge->getItems();
|
||||
|
||||
// Transform "legacy" items to FeedItems if necessary.
|
||||
// Remove this code when support for "legacy" items ends!
|
||||
if(isset($items[0]) && is_array($items[0])) {
|
||||
$feedItems = [];
|
||||
$feedItems = array();
|
||||
|
||||
foreach($items as $item) {
|
||||
$feedItems[] = new FeedItem($item);
|
||||
$feedItems[] = new \FeedItem($item);
|
||||
}
|
||||
|
||||
$items = $feedItems;
|
||||
}
|
||||
$infos = [
|
||||
|
||||
$infos = array(
|
||||
'name' => $bridge->getName(),
|
||||
'uri' => $bridge->getURI(),
|
||||
'donationUri' => $bridge->getDonationURI(),
|
||||
'icon' => $bridge->getIcon()
|
||||
];
|
||||
} catch (\Exception $e) {
|
||||
if ($e instanceof HttpException) {
|
||||
// Reproduce (and log) these responses regardless of error output and report limit
|
||||
if ($e->getCode() === 429) {
|
||||
$this->logger->info(sprintf('Exception in DisplayAction(%s): %s', $bridge->getShortName(), create_sane_exception_message($e)));
|
||||
return new Response(render(__DIR__ . '/../templates/exception.html.php', ['e' => $e]), 429);
|
||||
}
|
||||
if ($e->getCode() === 503) {
|
||||
$this->logger->info(sprintf('Exception in DisplayAction(%s): %s', $bridge->getShortName(), create_sane_exception_message($e)));
|
||||
return new Response(render(__DIR__ . '/../templates/exception.html.php', ['e' => $e]), 503);
|
||||
}
|
||||
}
|
||||
$this->logger->error(sprintf('Exception in DisplayAction(%s)', $bridge->getShortName()), ['e' => $e]);
|
||||
$errorOutput = Configuration::getConfig('error', 'output');
|
||||
$reportLimit = Configuration::getConfig('error', 'report_limit');
|
||||
$errorCount = 1;
|
||||
if ($reportLimit > 1) {
|
||||
$errorCount = $this->logBridgeError($bridge->getName(), $e->getCode());
|
||||
}
|
||||
// Let clients know about the error if we are passed the report limit
|
||||
if ($errorCount >= $reportLimit) {
|
||||
if ($errorOutput === 'feed') {
|
||||
// Render the exception as a feed item
|
||||
$items[] = $this->createFeedItemFromException($e, $bridge);
|
||||
} elseif ($errorOutput === 'http') {
|
||||
return new Response(render(__DIR__ . '/../templates/exception.html.php', ['e' => $e]), 500);
|
||||
} elseif ($errorOutput === 'none') {
|
||||
// Do nothing (produces an empty feed)
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
} catch(Error $e) {
|
||||
error_log($e);
|
||||
|
||||
$format->setItems($items);
|
||||
$format->setExtraInfos($infos);
|
||||
$now = time();
|
||||
$format->setLastModified($now);
|
||||
$headers = [
|
||||
'last-modified' => gmdate('D, d M Y H:i:s ', $now) . 'GMT',
|
||||
'content-type' => $format->getMimeType() . '; charset=' . $format->getCharset(),
|
||||
];
|
||||
return new Response($format->stringify(), 200, $headers);
|
||||
}
|
||||
if(logBridgeError($bridge::NAME, $e->getCode()) >= Configuration::getConfig('error', 'report_limit')) {
|
||||
if(Configuration::getConfig('error', 'output') === 'feed') {
|
||||
$item = new \FeedItem();
|
||||
|
||||
private function createFeedItemFromException($e, BridgeAbstract $bridge): FeedItem
|
||||
{
|
||||
$item = new FeedItem();
|
||||
// Create "new" error message every 24 hours
|
||||
$this->userData['_error_time'] = urlencode((int)(time() / 86400));
|
||||
|
||||
// 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());
|
||||
|
||||
// Create an item identifier for feed readers e.g. "staysafetv twitch videos_19389"
|
||||
$item->setUid($bridge->getName() . '_' . $uniqueIdentifier);
|
||||
|
||||
$content = render_template(__DIR__ . '/../templates/bridge-error.html.php', [
|
||||
'error' => render_template(__DIR__ . '/../templates/exception.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;
|
||||
}
|
||||
|
||||
private function logBridgeError($bridgeName, $code)
|
||||
{
|
||||
$cacheKey = 'error_reporting_' . $bridgeName . '_' . $code;
|
||||
$report = $this->cache->get($cacheKey);
|
||||
if ($report) {
|
||||
$report = Json::decode($report);
|
||||
$report['time'] = time();
|
||||
$report['count']++;
|
||||
// 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 {
|
||||
$report = [
|
||||
'error' => $code,
|
||||
'time' => time(),
|
||||
'count' => 1,
|
||||
];
|
||||
}
|
||||
$ttl = 86400 * 5;
|
||||
$this->cache->set($cacheKey, Json::encode($report), $ttl);
|
||||
return $report['count'];
|
||||
}
|
||||
|
||||
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(),
|
||||
]));
|
||||
}
|
||||
|
||||
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())
|
||||
$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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,89 +0,0 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* This action is used by the frontpage form search.
|
||||
* It finds a bridge based off of a user input url.
|
||||
* It uses bridges' detectParameters implementation.
|
||||
*/
|
||||
class FindfeedAction implements ActionInterface
|
||||
{
|
||||
public function execute(array $request)
|
||||
{
|
||||
$targetURL = $request['url'] ?? null;
|
||||
$format = $request['format'] ?? null;
|
||||
|
||||
if (!$targetURL) {
|
||||
return new Response('You must specify a url', 400);
|
||||
}
|
||||
if (!$format) {
|
||||
return new Response('You must specify a format', 400);
|
||||
}
|
||||
|
||||
$bridgeFactory = new BridgeFactory();
|
||||
|
||||
$results = [];
|
||||
foreach ($bridgeFactory->getBridgeClassNames() as $bridgeClassName) {
|
||||
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
|
||||
$bridgeParams = $bridge->detectParameters($targetURL);
|
||||
|
||||
if ($bridgeParams === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// It's allowed to have no 'context' in a bridge (only a default context without any name)
|
||||
// In this case, the reference to the parameters are found in the first element of the PARAMETERS array
|
||||
|
||||
$context = $bridgeParams['context'] ?? 0;
|
||||
|
||||
$bridgeData = [];
|
||||
// Construct the array of parameters
|
||||
foreach ($bridgeParams as $key => $value) {
|
||||
// 'context' is a special case : it's a bridge parameters, there is no "name" for this parameter
|
||||
if ($key == 'context') {
|
||||
$bridgeData[$key]['name'] = 'Context';
|
||||
$bridgeData[$key]['value'] = $value;
|
||||
} else {
|
||||
$bridgeData[$key]['name'] = $this->getParameterName($bridge, $context, $key);
|
||||
$bridgeData[$key]['value'] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$bridgeParams['bridge'] = $bridgeClassName;
|
||||
$bridgeParams['format'] = $format;
|
||||
$content = [
|
||||
'url' => get_home_page_url() . '?action=display&' . http_build_query($bridgeParams),
|
||||
'bridgeParams' => $bridgeParams,
|
||||
'bridgeData' => $bridgeData,
|
||||
'bridgeMeta' => [
|
||||
'name' => $bridge::NAME,
|
||||
'description' => $bridge::DESCRIPTION,
|
||||
'parameters' => $bridge::PARAMETERS,
|
||||
'icon' => $bridge->getIcon(),
|
||||
],
|
||||
];
|
||||
$results[] = $content;
|
||||
}
|
||||
if ($results === []) {
|
||||
return new Response(Json::encode(['message' => 'No bridge found for given url']), 404, ['content-type' => 'application/json']);
|
||||
}
|
||||
return new Response(Json::encode($results), 200, ['content-type' => 'application/json']);
|
||||
}
|
||||
|
||||
// Get parameter name in the actual context, or in the global parameter
|
||||
private function getParameterName($bridge, $context, $key)
|
||||
{
|
||||
if (isset($bridge::PARAMETERS[$context][$key]['name'])) {
|
||||
$name = $bridge::PARAMETERS[$context][$key]['name'];
|
||||
} else if (isset($bridge::PARAMETERS['global'][$key]['name'])) {
|
||||
$name = $bridge::PARAMETERS['global'][$key]['name'];
|
||||
} else {
|
||||
$name = 'Variable "' . $key . '" (No name provided)';
|
||||
}
|
||||
return $name;
|
||||
}
|
||||
}
|
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
final class FrontpageAction implements ActionInterface
|
||||
{
|
||||
public function execute(array $request)
|
||||
{
|
||||
$messages = [];
|
||||
$showInactive = (bool) ($request['show_inactive'] ?? null);
|
||||
$activeBridges = 0;
|
||||
|
||||
$bridgeFactory = new BridgeFactory();
|
||||
$bridgeClassNames = $bridgeFactory->getBridgeClassNames();
|
||||
|
||||
foreach ($bridgeFactory->getMissingEnabledBridges() as $missingEnabledBridge) {
|
||||
$messages[] = [
|
||||
'body' => sprintf('Warning : Bridge "%s" not found', $missingEnabledBridge),
|
||||
'level' => 'warning'
|
||||
];
|
||||
}
|
||||
|
||||
$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' => $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,
|
||||
]);
|
||||
}
|
||||
}
|
@@ -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']);
|
||||
}
|
||||
}
|
@@ -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.
|
||||
@@ -12,21 +11,33 @@
|
||||
* @link https://github.com/rss-bridge/rss-bridge
|
||||
*/
|
||||
|
||||
class ListAction implements ActionInterface
|
||||
{
|
||||
public function execute(array $request)
|
||||
{
|
||||
$list = new \stdClass();
|
||||
$list->bridges = [];
|
||||
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',
|
||||
$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(),
|
||||
@@ -34,9 +45,13 @@ class ListAction implements ActionInterface
|
||||
'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']);
|
||||
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($list, JSON_PRETTY_PRINT);
|
||||
}
|
||||
}
|
||||
|
@@ -1,58 +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
|
||||
{
|
||||
private CacheInterface $cache;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->cache = RssBridge::getCache();
|
||||
}
|
||||
|
||||
public function execute(array $request)
|
||||
{
|
||||
$authenticationMiddleware = new ApiAuthenticationMiddleware();
|
||||
$authenticationMiddleware($request);
|
||||
|
||||
$key = $request['key'] ?? null;
|
||||
if (!$key) {
|
||||
returnClientError('You must specify key!');
|
||||
}
|
||||
|
||||
$bridgeFactory = new BridgeFactory();
|
||||
|
||||
$bridgeName = $request['bridge'] ?? null;
|
||||
$bridgeClassName = $bridgeFactory->createBridgeClassName($bridgeName);
|
||||
if (!$bridgeClassName) {
|
||||
throw new \Exception(sprintf('Bridge not found: %s', $bridgeName));
|
||||
}
|
||||
|
||||
// whitelist control
|
||||
if (!$bridgeFactory->isEnabled($bridgeClassName)) {
|
||||
throw new \Exception('This bridge is not whitelisted', 401);
|
||||
}
|
||||
|
||||
$bridge = $bridgeFactory->create($bridgeClassName);
|
||||
$bridge->loadConfiguration();
|
||||
$value = $request['value'];
|
||||
|
||||
$cacheKey = get_class($bridge) . '_' . $key;
|
||||
$ttl = 86400 * 3;
|
||||
$this->cache->set($cacheKey, $value, $ttl);
|
||||
|
||||
header('Content-Type: text/plain');
|
||||
echo 'done';
|
||||
}
|
||||
}
|
4
app.json
4
app.json
@@ -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"]
|
||||
}
|
||||
|
||||
|
@@ -1,19 +1,17 @@
|
||||
<?php
|
||||
|
||||
class ABCNewsBridge extends BridgeAbstract
|
||||
{
|
||||
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 = [
|
||||
[
|
||||
'topic' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'topic' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Region',
|
||||
'title' => 'Choose state',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'ACT' => 'act',
|
||||
'NSW' => 'nsw',
|
||||
'NT' => 'nt',
|
||||
@@ -22,28 +20,26 @@ class ABCNewsBridge extends BridgeAbstract
|
||||
'TAS' => 'tas',
|
||||
'VIC' => 'vic',
|
||||
'WA' => 'wa'
|
||||
],
|
||||
]
|
||||
]
|
||||
];
|
||||
),
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
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),
|
||||
];
|
||||
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());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,76 +1,49 @@
|
||||
<?php
|
||||
|
||||
class AO3Bridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
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' => [
|
||||
'user' => [
|
||||
),
|
||||
),
|
||||
'Bookmarks' => array(
|
||||
'user' => array(
|
||||
'name' => 'user',
|
||||
'required' => true,
|
||||
// Example: Nyaaru's bookmarks
|
||||
'exampleValue' => 'Nyaaru',
|
||||
],
|
||||
],
|
||||
'Work' => [
|
||||
'id' => [
|
||||
),
|
||||
),
|
||||
'Work' => array(
|
||||
'id' => array(
|
||||
'name' => 'id',
|
||||
'required' => true,
|
||||
// Example: latest chapters from A Better Past by LysSerris
|
||||
'exampleValue' => '18181853',
|
||||
],
|
||||
]
|
||||
];
|
||||
private $title;
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
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)
|
||||
{
|
||||
// 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 = [];
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('div h4 a', 0);
|
||||
if (!isset($title)) {
|
||||
continue; // discard deleted works
|
||||
}
|
||||
if (!isset($title)) continue; // discard deleted works
|
||||
$item['title'] = $title->plaintext;
|
||||
$item['content'] = $element;
|
||||
$item['uri'] = $title->href;
|
||||
@@ -87,26 +60,16 @@ class AO3Bridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Feed for recent chapters of a specific work.
|
||||
*/
|
||||
private function collectWork($id)
|
||||
{
|
||||
// Feed for recent chapters of a specific work.
|
||||
private function collectWork($id) {
|
||||
$url = self::URI . "/works/$id/navigate";
|
||||
$httpClient = RssBridge::getHttpClient();
|
||||
|
||||
$version = 'v0.0.1';
|
||||
$response = $httpClient->request($url, [
|
||||
'useragent' => "rss-bridge $version (https://github.com/RSS-Bridge/rss-bridge)",
|
||||
]);
|
||||
|
||||
$html = \str_get_html($response->getBody());
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$this->title = $html->find('h2 a', 0)->plaintext;
|
||||
|
||||
foreach($html->find('ol.index.group > li') as $element) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $element->find('a', 0)->plaintext;
|
||||
$item['content'] = $element;
|
||||
@@ -125,17 +88,31 @@ class AO3Bridge extends BridgeAbstract
|
||||
$this->items = array_reverse($this->items);
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
$name = parent::getName() . " $this->queriedContext";
|
||||
if (isset($this->title)) {
|
||||
$name .= " - $this->title";
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Bookmarks':
|
||||
$user = $this->getInput('user');
|
||||
$this->title = $user;
|
||||
$url = self::URI
|
||||
. '/users/' . $user
|
||||
. '/bookmarks?bookmark_search[sort_column]=bookmarkable_date';
|
||||
return $this->collectList($url);
|
||||
case 'List': return $this->collectList(
|
||||
$this->getInput('url')
|
||||
);
|
||||
case 'Work': return $this->collectWork(
|
||||
$this->getInput('id')
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
$name = parent::getName() . " $this->queriedContext";
|
||||
if (isset($this->title)) $name .= " - $this->title";
|
||||
return $name;
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return self::URI . '/favicon.ico';
|
||||
}
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
class ARDMediathekBridge extends BridgeAbstract
|
||||
{
|
||||
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';
|
||||
@@ -41,19 +39,18 @@ class ARDMediathekBridge extends BridgeAbstract
|
||||
*/
|
||||
const IMAGEWIDTHPLACEHOLDER = '{width}';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'path' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'path' => array(
|
||||
'name' => 'Show Link or ID',
|
||||
'required' => true,
|
||||
'title' => 'Link to the show page or just its alphanumeric suffix',
|
||||
'defaultValue' => 'https://www.ardmediathek.de/sendung/45-min/Y3JpZDovL25kci5kZS8xMzkx/'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$oldTz = date_default_timezone_get();
|
||||
|
||||
date_default_timezone_set('Europe/Berlin');
|
||||
@@ -72,20 +69,20 @@ class ARDMediathekBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
$url = self::APIENDPOINT . $showID . '/?pageSize=' . self::PAGESIZE;
|
||||
$url = SELF::APIENDPOINT . $showID . '/?pageSize=' . SELF::PAGESIZE;
|
||||
$rawJSON = getContents($url);
|
||||
$processedJSON = json_decode($rawJSON);
|
||||
|
||||
foreach($processedJSON->teasers as $video) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
// there is also ->links->self->id, ->links->self->urlId, ->links->target->id, ->links->target->urlId
|
||||
$item['uri'] = self::VIDEOLINKPREFIX . $video->id . '/';
|
||||
$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['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;
|
||||
|
@@ -1,23 +1,21 @@
|
||||
<?php
|
||||
|
||||
class ASRockNewsBridge extends BridgeAbstract
|
||||
{
|
||||
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 PARAMETERS = array();
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI . '/news/index.asp');
|
||||
|
||||
$html = defaultLinkTo($html, self::URI . '/news/');
|
||||
|
||||
foreach($html->find('div.inner > a') as $index => $a) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$articlePath = $a->href;
|
||||
|
||||
@@ -34,12 +32,7 @@ class ASRockNewsBridge extends BridgeAbstract
|
||||
|
||||
$item['content'] = $contents->innertext;
|
||||
$item['timestamp'] = $this->extractDate($a->plaintext);
|
||||
|
||||
$img = $a->find('img', 0);
|
||||
if ($img) {
|
||||
$item['enclosures'][] = $img->src;
|
||||
}
|
||||
|
||||
$item['enclosures'][] = $a->find('img', 0)->src;
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
@@ -48,8 +41,7 @@ class ASRockNewsBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function extractDate($text)
|
||||
{
|
||||
private function extractDate($text) {
|
||||
$dateRegex = '/^([0-9]{4}\/[0-9]{1,2}\/[0-9]{1,2})/';
|
||||
|
||||
$text = trim($text);
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
class AcrimedBridge extends FeedExpander {
|
||||
|
||||
class AcrimedBridge extends FeedExpander
|
||||
{
|
||||
const MAINTAINER = 'qwertygc';
|
||||
const NAME = 'Acrimed Bridge';
|
||||
const URI = 'https://www.acrimed.org/';
|
||||
@@ -18,16 +17,14 @@ class AcrimedBridge extends FeedExpander
|
||||
]
|
||||
];
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas(
|
||||
static::URI . 'spip.php?page=backend',
|
||||
$this->getInput('limit')
|
||||
);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem)
|
||||
{
|
||||
protected function parseItem($newsItem){
|
||||
$item = parent::parseItem($newsItem);
|
||||
|
||||
$articlePage = getSimpleHTMLDOM($newsItem->link);
|
||||
|
@@ -1,17 +1,16 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array(
|
||||
'Publications' => array(
|
||||
'theme' => array(
|
||||
'name' => 'Thematique',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Tout' => '',
|
||||
'Rapport d\'activite' => 'rapport-dactivite',
|
||||
'Etude' => 'etudes',
|
||||
@@ -19,24 +18,22 @@ class AirBreizhBridge extends BridgeAbstract
|
||||
'Autres documents' => 'autres-documents',
|
||||
'Plan Régional de Surveillance de la qualité de l’air' => 'prsqa',
|
||||
'Transport' => 'transport'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return 'https://www.airbreizh.asso.fr/voy_content/uploads/2017/11/favicon.png';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
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 = [];
|
||||
$item = array();
|
||||
// Title
|
||||
$item['title'] = $article->find('h2', 0)->plaintext;
|
||||
// Author
|
||||
|
@@ -1,25 +1,24 @@
|
||||
<?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 PARAMETERS = [ [
|
||||
'postcount' => [
|
||||
const PARAMETERS = array( array(
|
||||
'postcount' => array(
|
||||
'name' => 'Limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'title' => 'Maximum number of items to return',
|
||||
'defaultValue' => 5,
|
||||
],
|
||||
'language' => [
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Deutsch' => 'de',
|
||||
'Polski' => 'pl',
|
||||
@@ -27,20 +26,19 @@ class AlbionOnlineBridge extends BridgeAbstract
|
||||
'Русский' => 'ru',
|
||||
'Português' => 'pt',
|
||||
'Español' => 'es',
|
||||
],
|
||||
),
|
||||
'title' => 'Language of changelog posts',
|
||||
'defaultValue' => 'en',
|
||||
],
|
||||
'full' => [
|
||||
),
|
||||
'full' => array(
|
||||
'name' => 'Full changelog',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'title' => 'Enable to receive the full changelog post for each item'
|
||||
],
|
||||
]];
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
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');
|
||||
@@ -48,7 +46,7 @@ class AlbionOnlineBridge extends BridgeAbstract
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
foreach ($html->find('li') as $data) {
|
||||
$item = [];
|
||||
$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
|
||||
@@ -67,8 +65,7 @@ class AlbionOnlineBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function getFullChangelog($url)
|
||||
{
|
||||
private function getFullChangelog($url) {
|
||||
$html = getSimpleHTMLDOMCached($url);
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
return $html->find('div.small-12.columns', 1)->innertext;
|
||||
|
@@ -1,35 +1,33 @@
|
||||
<?php
|
||||
class AlfaBankByBridge extends BridgeAbstract {
|
||||
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'News' => array(
|
||||
'business' => array(
|
||||
'name' => 'Альфа Бизнес',
|
||||
'type' => 'list',
|
||||
'title' => 'В зависимости от выбора, возращает уведомления для" .
|
||||
" клиентов физ. лиц либо для клиентов-юридических лиц и ИП',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Новости' => 'news',
|
||||
'Новости бизнеса' => 'newsBusiness'
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'news'
|
||||
],
|
||||
'fullContent' => [
|
||||
),
|
||||
'fullContent' => array(
|
||||
'name' => 'Включать содержимое',
|
||||
'type' => 'checkbox',
|
||||
'title' => 'Если выбрано, содержимое уведомлений вставляется в поток (работает медленно)'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$business = $this->getInput('business') == 'newsBusiness';
|
||||
$fullContent = $this->getInput('fullContent') == 'on';
|
||||
|
||||
@@ -42,7 +40,7 @@ class AlfaBankByBridge extends BridgeAbstract
|
||||
|
||||
foreach($html->find('a.notifications__item') as $element) {
|
||||
if($limit < 10) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uid'] = 'urn:sha1:' . hash('sha1', $element->getAttribute('data-notification-id'));
|
||||
$item['title'] = $element->find('div.item-title', 0)->innertext;
|
||||
$item['timestamp'] = DateTime::createFromFormat(
|
||||
@@ -69,19 +67,17 @@ class AlfaBankByBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return static::URI . '/local/images/favicon.ico';
|
||||
}
|
||||
|
||||
private function ruMonthsToEn($date)
|
||||
{
|
||||
$ruMonths = [
|
||||
private function ruMonthsToEn($date) {
|
||||
$ruMonths = array(
|
||||
'Января', 'Февраля', 'Марта', 'Апреля', 'Мая', 'Июня',
|
||||
'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря' ];
|
||||
$enMonths = [
|
||||
'Июля', 'Августа', 'Сентября', 'Октября', 'Ноября', 'Декабря' );
|
||||
$enMonths = array(
|
||||
'January', 'February', 'March', 'April', 'May', 'June',
|
||||
'July', 'August', 'September', 'October', 'November', 'December' ];
|
||||
'July', 'August', 'September', 'October', 'November', 'December' );
|
||||
return str_replace($ruMonths, $enMonths, $date);
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -1,147 +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' => '^.{70,};?$',
|
||||
// phpcs:ignore
|
||||
'exampleValue' => 'v4.1-oCrmXTMqv2ppC21GTUCKLmUwRPP1ssQVALKuqwsZ1VXjcKgL2vO5TTRM5xMxS9GiyqxF1gAeyc-63dl0coUoBKXCXi_nAmr95yyqGpq2RAFoneZ4L399E8n6iYyemcuGARjAoSfjvLHJCEwvvHHynSgaxlFBu7hUnKfuy39zo9sSQdyTUjotJg3CAZ53q9v2raAnPCyGOAR4ytRILd9p24EJnxp7_oR0XbVPIo1hDa4WmjXFOxph8rHaO5tWd',
|
||||
'required' => false,
|
||||
],
|
||||
'includeSponsoredOffers' => [
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Include Sponsored Offers',
|
||||
'defaultValue' => 'checked'
|
||||
],
|
||||
'includePromotedOffers' => [
|
||||
'type' => 'checkbox',
|
||||
'name' => 'Include Promoted Offers',
|
||||
'defaultValue' => 'checked'
|
||||
]
|
||||
]];
|
||||
|
||||
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('article[data-analytics-view-custom-context="REGULAR"]');
|
||||
|
||||
if (!$this->getInput('includeSponsoredOffers')) {
|
||||
$results = array_merge($results, $html->find('article[data-analytics-view-custom-context="SPONSORED"]'));
|
||||
}
|
||||
|
||||
if (!$this->getInput('includePromotedOffers')) {
|
||||
$results = array_merge($results, $html->find('article[data-analytics-view-custom-context="PROMOTED"]'));
|
||||
}
|
||||
|
||||
foreach ($results as $post) {
|
||||
$item = [];
|
||||
|
||||
$item['uid'] = $post->{'data-analytics-view-value'};
|
||||
|
||||
$item_link = $post->find('a[href*="' . $item['uid'] . '"], a[href*="allegrolokalnie"]', 0);
|
||||
|
||||
$item['uri'] = $item_link->href;
|
||||
|
||||
$item['title'] = $item_link->find('img', 0)->alt;
|
||||
|
||||
$image = $item_link->find('img', 0)->{'data-src'} ?: $item_link->find('img', 0)->src ?? false;
|
||||
|
||||
if ($image) {
|
||||
$item['enclosures'] = [$image . '#.image'];
|
||||
}
|
||||
|
||||
$price = $post->{'data-analytics-view-json-custom-price'};
|
||||
if ($price) {
|
||||
$priceDecoded = json_decode(html_entity_decode($price));
|
||||
$price = $priceDecoded->amount . ' ' . $priceDecoded->currency;
|
||||
}
|
||||
|
||||
$descriptionPatterns = ['/<\s*dt[^>]*>\b/', '/<\/dt>/', '/<\s*dd[^>]*>\b/', '/<\/dd>/'];
|
||||
$descriptionReplacements = ['<span>', ':</span> ', '<strong>', ' </strong> '];
|
||||
$description = $post->find('.m7er_k4.mpof_5r.mpof_z0_s', 0)->innertext;
|
||||
$descriptionPretty = preg_replace($descriptionPatterns, $descriptionReplacements, $description);
|
||||
|
||||
$pricingExtraInfo = array_filter($post->find('.mqu1_g3.mgn2_12'), function ($node) {
|
||||
return empty($node->find('.mvrt_0'));
|
||||
});
|
||||
|
||||
$pricingExtraInfo = $pricingExtraInfo[0]->plaintext ?? '';
|
||||
|
||||
$offerExtraInfo = array_map(function ($node) {
|
||||
return str_contains($node->plaintext, 'zapłać później') ? '' : $node->outertext;
|
||||
}, $post->find('div.mpof_ki.mwdn_1.mj7a_4.mgn2_12'));
|
||||
|
||||
$isSmart = $post->find('img[alt="Smart!"]', 0) ?? false;
|
||||
if ($isSmart) {
|
||||
$pricingExtraInfo .= $isSmart->outertext;
|
||||
}
|
||||
|
||||
$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>'
|
||||
. implode('</div><div>', $offerExtraInfo)
|
||||
. '</div><dl>'
|
||||
. $pricingExtraInfo
|
||||
. '</dl><hr>';
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,18 +1,17 @@
|
||||
<?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 URI = 'https://www.allocine.fr/';
|
||||
const DESCRIPTION = 'Bridge for allocine.fr';
|
||||
const PARAMETERS = [ [
|
||||
'category' => [
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
'name' => 'Emission',
|
||||
'type' => 'list',
|
||||
'title' => 'Sélectionner l\'emission',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Faux Raccord' => 'faux-raccord',
|
||||
'Fanzone' => 'fanzone',
|
||||
'Game In Ciné' => 'game-in-cine',
|
||||
@@ -28,34 +27,34 @@ class AllocineFRBridge extends BridgeAbstract
|
||||
'Complètement...' => 'completement',
|
||||
'#Fun Facts' => 'fun-facts',
|
||||
'Origin Story' => 'origin-story',
|
||||
]
|
||||
]
|
||||
]];
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
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/'
|
||||
];
|
||||
|
||||
$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/'
|
||||
);
|
||||
|
||||
$category = $this->getInput('category');
|
||||
if(array_key_exists($category, $categories)) {
|
||||
return static::URI . $this->getLastSeasonURI($categories[$category]);
|
||||
return static::URI . $categories[$category];
|
||||
} else {
|
||||
returnClientError('Emission inconnue');
|
||||
}
|
||||
@@ -64,32 +63,32 @@ class AllocineFRBridge extends BridgeAbstract
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
private function getLastSeasonURI($category)
|
||||
{
|
||||
$html = getSimpleHTMLDOMCached(static::URI . $category, 86400);
|
||||
$seasonLink = $html->find('section[class=section-wrap section]', 0)->find('div[class=cf]', 0)->find('a', 0);
|
||||
$URI = $seasonLink->href;
|
||||
return $URI;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('category'))) {
|
||||
return self::NAME . ' : ' . $this->getKey('category');
|
||||
return self::NAME . ' : '
|
||||
. array_search(
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$category = array_search(
|
||||
$this->getInput('category'),
|
||||
self::PARAMETERS[$this->queriedContext]['category']['values']
|
||||
);
|
||||
|
||||
foreach($html->find('div[class=gd-col-left]', 0)->find('div[class*=video-card]') as $element) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$title = $element->find('a[class*=meta-title-link]', 0);
|
||||
$content = trim(defaultLinkTo($element->outertext, static::URI));
|
||||
$content = trim($element->outertext);
|
||||
|
||||
// Replace image 'src' with the one in 'data-src'
|
||||
$content = preg_replace('@src="data:image/gif;base64,[A-Za-z0-9+\/]*"@', '', $content);
|
||||
@@ -100,7 +99,7 @@ class AllocineFRBridge extends BridgeAbstract
|
||||
|
||||
$item['content'] = $content;
|
||||
$item['title'] = trim($title->innertext);
|
||||
$item['uri'] = static::URI . '/' . substr($title->href, 1);
|
||||
$item['uri'] = static::URI . substr($title->href, 1);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
@@ -1,66 +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);
|
||||
$date = $element->find('span.date', 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['timestamp'] = $this->frenchPubDateToTimestamp($date->plaintext);
|
||||
$item['uri'] = static::BASE_URI . '/' . substr($title->href, 1);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
private function frenchPubDateToTimestamp($date)
|
||||
{
|
||||
return strtotime(
|
||||
strtr(
|
||||
strtolower($date),
|
||||
[
|
||||
'janvier' => 'jan',
|
||||
'février' => 'feb',
|
||||
'mars' => 'march',
|
||||
'avril' => 'apr',
|
||||
'mai' => 'may',
|
||||
'juin' => 'jun',
|
||||
'juillet' => 'jul',
|
||||
'août' => 'aug',
|
||||
'septembre' => 'sep',
|
||||
'octobre' => 'oct',
|
||||
'novembre' => 'nov',
|
||||
'décembre' => 'dec'
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@@ -1,35 +1,35 @@
|
||||
<?php
|
||||
|
||||
class AmazonBridge extends BridgeAbstract
|
||||
{
|
||||
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';
|
||||
|
||||
const PARAMETERS = [[
|
||||
'q' => [
|
||||
const PARAMETERS = array(array(
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'required' => true,
|
||||
'exampleValue' => 'watch',
|
||||
],
|
||||
'sort' => [
|
||||
),
|
||||
'sort' => array(
|
||||
'name' => 'Sort by',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'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' => [
|
||||
),
|
||||
'tld' => array(
|
||||
'name' => 'Country',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Australia' => 'com.au',
|
||||
'Brazil' => 'com.br',
|
||||
'Canada' => 'ca',
|
||||
@@ -41,65 +41,55 @@ class AmazonBridge extends BridgeAbstract
|
||||
'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',
|
||||
],
|
||||
]];
|
||||
),
|
||||
));
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$baseUrl = sprintf('https://www.amazon.%s', $this->getInput('tld'));
|
||||
|
||||
$url = sprintf(
|
||||
'%s/s/?field-keywords=%s&sort=%s',
|
||||
$baseUrl,
|
||||
urlencode($this->getInput('q')),
|
||||
$this->getInput('sort')
|
||||
);
|
||||
|
||||
$dom = getSimpleHTMLDOM($url);
|
||||
|
||||
$elements = $dom->find('div.s-result-item');
|
||||
|
||||
foreach ($elements as $element) {
|
||||
$item = [];
|
||||
|
||||
$title = $element->find('h2', 0);
|
||||
if (!$title) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = $title->innertext;
|
||||
|
||||
$itemUrl = $element->find('a', 0)->href;
|
||||
$item['uri'] = urljoin($baseUrl, $itemUrl);
|
||||
|
||||
$image = $element->find('img', 0);
|
||||
if ($image) {
|
||||
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />';
|
||||
}
|
||||
|
||||
$price = $element->find('span.a-price > .a-offscreen', 0);
|
||||
if ($price) {
|
||||
$item['content'] .= $price->innertext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('tld')) && !is_null($this->getInput('q'))) {
|
||||
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$uri = 'https://www.amazon.' . $this->getInput('tld') . '/';
|
||||
$uri .= 's/?field-keywords=' . urlencode($this->getInput('q')) . '&sort=' . $this->getInput('sort');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
||||
foreach($html->find('li.s-result-item') as $element) {
|
||||
|
||||
$item = array();
|
||||
|
||||
// Title
|
||||
$title = $element->find('h2', 0);
|
||||
if (is_null($title)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = html_entity_decode($title->innertext, ENT_QUOTES);
|
||||
|
||||
// Url
|
||||
$uri = $title->parent()->getAttribute('href');
|
||||
$uri = substr($uri, 0, strrpos($uri, '/'));
|
||||
|
||||
$item['uri'] = substr($uri, 0, strrpos($uri, '/'));
|
||||
|
||||
// Content
|
||||
$image = $element->find('img', 0);
|
||||
$price = $element->find('span.s-price', 0);
|
||||
$price = ($price) ? $price->innertext : '';
|
||||
|
||||
$item['content'] = '<img src="' . $image->getAttribute('src') . '" /><br />' . $price;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,26 +1,25 @@
|
||||
<?php
|
||||
|
||||
class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
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' => [
|
||||
),
|
||||
'tld' => array(
|
||||
'name' => 'Country',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Australia' => 'com.au',
|
||||
'Brazil' => 'com.br',
|
||||
'Canada' => 'ca',
|
||||
@@ -32,25 +31,23 @@ class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
'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 PRICE_SELECTORS = [
|
||||
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";
|
||||
|
||||
@@ -59,16 +56,14 @@ class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
/**
|
||||
* Generates domain name given a amazon TLD
|
||||
*/
|
||||
private function getDomainName()
|
||||
{
|
||||
private function getDomainName() {
|
||||
return 'https://www.amazon.' . $this->getInput('tld');
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates URI for a Amazon product page
|
||||
*/
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('asin'))) {
|
||||
return $this->getDomainName() . '/dp/' . $this->getInput('asin');
|
||||
}
|
||||
@@ -79,8 +74,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
* Scrapes the product title from the html page
|
||||
* returns the default title if scraping fails
|
||||
*/
|
||||
private function getTitle($html)
|
||||
{
|
||||
private function getTitle($html) {
|
||||
$titleTag = $html->find('#productTitle', 0);
|
||||
|
||||
if (!$titleTag) {
|
||||
@@ -93,8 +87,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
/**
|
||||
* Title used by the feed if none could be found
|
||||
*/
|
||||
private function getDefaultTitle()
|
||||
{
|
||||
private function getDefaultTitle() {
|
||||
return 'Amazon.' . $this->getInput('tld') . ': ' . $this->getInput('asin');
|
||||
}
|
||||
|
||||
@@ -102,8 +95,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
* Returns name for the feed
|
||||
* Uses title (already scraped) if it has one
|
||||
*/
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
if (isset($this->title)) {
|
||||
return $this->title;
|
||||
} else {
|
||||
@@ -111,8 +103,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function parseDynamicImage($attribute)
|
||||
{
|
||||
private function parseDynamicImage($attribute) {
|
||||
$json = json_decode(html_entity_decode($attribute), true);
|
||||
|
||||
if ($json and count($json) > 0) {
|
||||
@@ -123,15 +114,15 @@ class AmazonPriceTrackerBridge extends BridgeAbstract
|
||||
/**
|
||||
* Returns a generated image tag for the product
|
||||
*/
|
||||
private function getImage($html)
|
||||
{
|
||||
$image = 'https://placekitten.com/200/300';
|
||||
private function getImage($html) {
|
||||
$imageSrc = $html->find('#main-image-container img', 0);
|
||||
|
||||
if ($imageSrc) {
|
||||
$hiresImage = $imageSrc->getAttribute('data-old-hires');
|
||||
$dynamicImageAttribute = $imageSrc->getAttribute('data-a-dynamic-image');
|
||||
$image = $hiresImage ?: $this->parseDynamicImage($dynamicImageAttribute);
|
||||
}
|
||||
$image = $image ?: 'https://placekitten.com/200/300';
|
||||
|
||||
return <<<EOT
|
||||
<img width="300" style="max-width:300;max-height:300" src="$image" alt="{$this->title}" />
|
||||
@@ -142,56 +133,46 @@ EOT;
|
||||
* Return \simple_html_dom object
|
||||
* for the entire html of the product page
|
||||
*/
|
||||
private function getHtml()
|
||||
{
|
||||
private function getHtml() {
|
||||
$uri = $this->getURI();
|
||||
|
||||
return getSimpleHTMLDOM($uri) ?: returnServerError('Could not request Amazon.');
|
||||
}
|
||||
|
||||
private function scrapePriceFromMetrics($html)
|
||||
{
|
||||
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 [
|
||||
return array(
|
||||
'price' => $asinData->getAttribute('data-asin-price'),
|
||||
'currency' => $asinData->getAttribute('data-asin-currency-code'),
|
||||
'shipping' => $asinData->getAttribute('data-asin-shipping')
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function scrapePriceTwister($html)
|
||||
{
|
||||
private function scrapePriceTwister($html) {
|
||||
$str = $html->find('.twister-plus-buying-options-price-data', 0);
|
||||
|
||||
$data = json_decode($str->innertext, true);
|
||||
if(count($data) === 1) {
|
||||
$data = $data[0];
|
||||
return [
|
||||
return array(
|
||||
'displayPrice' => $data['displayPrice'],
|
||||
'currency' => $data['currency'],
|
||||
'shipping' => '0',
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function scrapePriceGeneric($html)
|
||||
{
|
||||
$default = [
|
||||
'price' => null,
|
||||
'displayPrice' => null,
|
||||
'currency' => null,
|
||||
'shipping' => null,
|
||||
];
|
||||
private function scrapePriceGeneric($html) {
|
||||
$priceDiv = null;
|
||||
|
||||
foreach(self::PRICE_SELECTORS as $sel) {
|
||||
@@ -202,51 +183,59 @@ EOT;
|
||||
}
|
||||
|
||||
if (!$priceDiv) {
|
||||
return $default;
|
||||
return false;
|
||||
}
|
||||
|
||||
$priceString = str_replace(str_split(self::WHITESPACE), '', $priceDiv->plaintext);
|
||||
preg_match('/(\d+\.\d{0,2})/', $priceString, $matches);
|
||||
|
||||
$price = $matches[0] ?? null;
|
||||
$price = $matches[0];
|
||||
$currency = str_replace($price, '', $priceString);
|
||||
|
||||
if ($price != null && $currency != null) {
|
||||
return [
|
||||
return array(
|
||||
'price' => $price,
|
||||
'displayPrice' => null,
|
||||
'currency' => $currency,
|
||||
'shipping' => '0'
|
||||
];
|
||||
}
|
||||
return $default;
|
||||
);
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = $this->getHtml();
|
||||
$this->title = $this->getTitle($html);
|
||||
$image = $this->getImage($html);
|
||||
$data = $this->scrapePriceGeneric($html);
|
||||
return false;
|
||||
}
|
||||
|
||||
// render
|
||||
$content = '';
|
||||
private function renderContent($image, $data) {
|
||||
$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']);
|
||||
$price = "{$data['price']} {$data['currency']}";
|
||||
}
|
||||
|
||||
$item = [
|
||||
$html = "$image<br>Price: $price";
|
||||
|
||||
if ($data['shipping'] !== '0') {
|
||||
$html .= "<br>Shipping: {$data['shipping']} {$data['currency']}</br>";
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrape method for Amazon product page
|
||||
* @return [type] [description]
|
||||
*/
|
||||
public function collectData() {
|
||||
$html = $this->getHtml();
|
||||
$this->title = $this->getTitle($html);
|
||||
$imageTag = $this->getImage($html);
|
||||
|
||||
$data = $this->scrapePriceGeneric($html);
|
||||
|
||||
$item = array(
|
||||
'title' => $this->title,
|
||||
'uri' => $this->getURI(),
|
||||
'content' => $content,
|
||||
'content' => $this->renderContent($imageTag, $data),
|
||||
// This is to ensure that feed readers notice the price change
|
||||
'uid' => md5($data['price'])
|
||||
];
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -1,19 +1,18 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'id' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'All categories' => '0',
|
||||
'Anime' => '1,2,3',
|
||||
'Anime - Sub' => '1',
|
||||
@@ -35,12 +34,12 @@ class AnidexBridge extends BridgeAbstract
|
||||
'Pictures' => '14',
|
||||
'Adult Video' => '15',
|
||||
'Other' => '16'
|
||||
]
|
||||
],
|
||||
'lang_id' => [
|
||||
)
|
||||
),
|
||||
'lang_id' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'All languages' => '0',
|
||||
'English' => '1',
|
||||
'Japanese' => '2',
|
||||
@@ -73,52 +72,52 @@ class AnidexBridge extends BridgeAbstract
|
||||
'Spanish (LATAM)' => '29',
|
||||
'Persian' => '30',
|
||||
'Malaysian' => '31'
|
||||
]
|
||||
],
|
||||
'group_id' => [
|
||||
)
|
||||
),
|
||||
'group_id' => array(
|
||||
'name' => 'Group ID',
|
||||
'type' => 'number'
|
||||
],
|
||||
'r' => [
|
||||
),
|
||||
'r' => array(
|
||||
'name' => 'Hide Remakes',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'b' => [
|
||||
),
|
||||
'b' => array(
|
||||
'name' => 'Only Batches',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'a' => [
|
||||
),
|
||||
'a' => array(
|
||||
'name' => 'Only Authorized',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'q' => [
|
||||
),
|
||||
'q' => array(
|
||||
'name' => 'Keyword',
|
||||
'description' => 'Keyword(s)',
|
||||
'type' => 'text'
|
||||
],
|
||||
'h' => [
|
||||
),
|
||||
'h' => array(
|
||||
'name' => 'Adult content',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'No filter' => '0',
|
||||
'Hide +18' => '1',
|
||||
'Only +18' => '2'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData() {
|
||||
|
||||
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) {
|
||||
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 (['r', 'b', 'a'] as $param_name) {
|
||||
foreach (array('r', 'b', 'a') as $param_name) {
|
||||
$param = $this->getInput($param_name);
|
||||
if (!empty($param) && boolval($param)) {
|
||||
$search_url .= '&' . $param_name . '=1';
|
||||
@@ -128,14 +127,14 @@ class AnidexBridge extends BridgeAbstract
|
||||
if (!empty($query)) {
|
||||
$search_url .= '&q=' . urlencode($query);
|
||||
}
|
||||
$opt = [];
|
||||
$opt = array();
|
||||
$h = $this->getInput('h');
|
||||
if (!empty($h) && intval($h) != 0 && ctype_digit($h)) {
|
||||
$opt[CURLOPT_COOKIE] = 'anidex_h_toggle=' . $h;
|
||||
}
|
||||
|
||||
// We need to use a different Host HTTP header to reach the correct page on ALTERNATE_URI
|
||||
$headers = ['Host: ' . self::ALTERNATE_HOST];
|
||||
$headers = array('Host: ' . self::ALTERNATE_HOST);
|
||||
|
||||
// The HTTPS certificate presented by anidex.moe is for anidex.info. We need to ignore this.
|
||||
// As a consequence, the bridge is intentionally marked as insecure by setting self::URI to http://
|
||||
@@ -145,18 +144,16 @@ class AnidexBridge extends BridgeAbstract
|
||||
// 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 = 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'))) {
|
||||
if (empty($results) && empty($this->getInput('q')))
|
||||
returnServerError('No results from Anidex: ' . $search_url);
|
||||
}
|
||||
|
||||
//Process each item individually
|
||||
foreach ($results as $element) {
|
||||
|
||||
//Limit total amount of requests
|
||||
if(count($this->items) >= 20) {
|
||||
break;
|
||||
@@ -166,12 +163,14 @@ class AnidexBridge extends BridgeAbstract
|
||||
|
||||
//Ignore entries without valid torrent ID
|
||||
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
|
||||
|
||||
//Retrieve data for this torrent ID
|
||||
$item_browse_uri = self::URI . 'torrent/' . $torrent_id;
|
||||
$item_fetch_uri = self::ALTERNATE_URI . 'torrent/' . $torrent_id;
|
||||
|
||||
//Retrieve full description from torrent page (cached for 24 hours: 86400 seconds)
|
||||
if ($item_html = getSimpleHTMLDOMCached($item_fetch_uri, 86400, $headers, $opt)) {
|
||||
|
||||
//Retrieve data from page contents
|
||||
$item_title = str_replace(' (Torrent) - AniDex ', '', $item_html->find('title', 0)->plaintext);
|
||||
$item_desc = $item_html->find('div.panel-body', 0);
|
||||
@@ -201,12 +200,12 @@ class AnidexBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
//Build and add final item
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $item_browse_uri;
|
||||
$item['title'] = $item_title;
|
||||
$item['author'] = $item_author;
|
||||
$item['timestamp'] = $item_date;
|
||||
$item['enclosures'] = [$item_image];
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_desc;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -1,31 +1,33 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array( array(
|
||||
'type' => array(
|
||||
'name' => 'Type',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Everything' => '',
|
||||
'Anime' => 'A',
|
||||
'Drama' => 'D',
|
||||
'Tokusatsu' => 'T'
|
||||
]
|
||||
]
|
||||
]];
|
||||
)
|
||||
)
|
||||
));
|
||||
|
||||
private $filter = 'Releases';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
|
||||
//Add type filter if provided
|
||||
$typeFilter = $this->getKey('type');
|
||||
$typeFilter = array_search(
|
||||
$this->getInput('type'),
|
||||
self::PARAMETERS[$this->queriedContext]['type']['values']
|
||||
);
|
||||
|
||||
//Build date and filters for making requests
|
||||
$thismonth = date('mY') . $typeFilter;
|
||||
@@ -33,7 +35,8 @@ class AnimeUltimeBridge extends BridgeAbstract
|
||||
|
||||
//Process each HTML page until having 10 releases
|
||||
$processedOK = 0;
|
||||
foreach ([$thismonth, $lastmonth] as $requestFilter) {
|
||||
foreach (array($thismonth, $lastmonth) as $requestFilter) {
|
||||
|
||||
$url = self::URI . 'history-0-1/' . $requestFilter;
|
||||
$html = getContents($url);
|
||||
// Convert html from iso-8859-1 => utf8
|
||||
@@ -42,6 +45,7 @@ class AnimeUltimeBridge extends BridgeAbstract
|
||||
|
||||
//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));
|
||||
@@ -57,6 +61,7 @@ class AnimeUltimeBridge extends BridgeAbstract
|
||||
//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;
|
||||
@@ -81,6 +86,7 @@ class AnimeUltimeBridge extends BridgeAbstract
|
||||
$item_type = $release->find('td', 4)->plaintext;
|
||||
|
||||
if(!empty($item_uri)) {
|
||||
|
||||
// Retrieve description from description page
|
||||
$html_item = getContents($item_uri);
|
||||
// Convert html from iso-8859-1 => utf8
|
||||
@@ -89,8 +95,7 @@ class AnimeUltimeBridge extends BridgeAbstract
|
||||
$html_item,
|
||||
strpos($html_item, 'class="principal_contain" align="center">') + 41
|
||||
);
|
||||
$item_description = substr(
|
||||
$item_description,
|
||||
$item_description = substr($item_description,
|
||||
0,
|
||||
strpos($item_description, '<div id="table">')
|
||||
);
|
||||
@@ -101,18 +106,18 @@ class AnimeUltimeBridge extends BridgeAbstract
|
||||
$item_description = str_replace("\n", '', $item_description);
|
||||
|
||||
//Build and add final item
|
||||
$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'] = [$item_image];
|
||||
$item['enclosures'] = array($item_image);
|
||||
$item['content'] = $item_description;
|
||||
$this->items[] = $item;
|
||||
$processedOK++;
|
||||
|
||||
//Stop processing once limit is reached
|
||||
if ($processedOK >= 10) {
|
||||
if ($processedOK >= 10)
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -120,12 +125,15 @@ class AnimeUltimeBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
if(!is_null($this->getInput('type'))) {
|
||||
return 'Latest ' . $this->getKey('type') . ' - Anime-Ultime Bridge';
|
||||
$typeFilter = array_search(
|
||||
$this->getInput('type'),
|
||||
self::PARAMETERS[$this->queriedContext]['type']['values']
|
||||
);
|
||||
|
||||
return 'Latest ' . $typeFilter . ' - Anime-Ultime Bridge';
|
||||
}
|
||||
|
||||
return parent::getName();
|
||||
|
@@ -1,23 +1,23 @@
|
||||
<?php
|
||||
|
||||
class AppleAppStoreBridge extends BridgeAbstract
|
||||
{
|
||||
class AppleAppStoreBridge extends BridgeAbstract {
|
||||
|
||||
const MAINTAINER = 'captn3m0';
|
||||
const NAME = 'Apple App Store';
|
||||
const URI = 'https://apps.apple.com/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'Returns version updates for a specific application';
|
||||
|
||||
const PARAMETERS = [[
|
||||
'id' => [
|
||||
const PARAMETERS = array(array(
|
||||
'id' => array(
|
||||
'name' => 'Application ID',
|
||||
'required' => true,
|
||||
'exampleValue' => '310633997'
|
||||
],
|
||||
'p' => [
|
||||
),
|
||||
'p' => array(
|
||||
'name' => 'Platform',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'iPad' => 'ipad',
|
||||
'iPhone' => 'iphone',
|
||||
'Mac' => 'mac',
|
||||
@@ -26,51 +26,36 @@ class AppleAppStoreBridge extends BridgeAbstract
|
||||
// but not yet tested
|
||||
'Web' => 'web',
|
||||
'Apple TV' => 'appletv',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'iphone',
|
||||
],
|
||||
'country' => [
|
||||
),
|
||||
'country' => array(
|
||||
'name' => 'Store Country',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'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 PLATFORM_MAPPING = [
|
||||
const PLATFORM_MAPPING = array(
|
||||
'iphone' => 'ios',
|
||||
'ipad' => 'ios',
|
||||
];
|
||||
);
|
||||
|
||||
private function makeHtmlUrl($id, $country)
|
||||
{
|
||||
private function makeHtmlUrl($id, $country){
|
||||
return 'https://apps.apple.com/' . $country . '/app/id' . $id;
|
||||
}
|
||||
|
||||
private function makeJsonUrl($id, $platform, $country)
|
||||
{
|
||||
private function makeJsonUrl($id, $platform, $country){
|
||||
return "https://amp-api.apps.apple.com/v1/catalog/$country/apps/$id?platform=$platform&extend=versionHistory";
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
if (isset($this->name)) {
|
||||
return $this->name . ' - AppStore Updates';
|
||||
}
|
||||
@@ -81,8 +66,7 @@ class AppleAppStoreBridge extends BridgeAbstract
|
||||
/**
|
||||
* In case of some platforms, the data is present in the initial response
|
||||
*/
|
||||
private function getDataFromShoebox($id, $platform, $country)
|
||||
{
|
||||
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);
|
||||
@@ -91,8 +75,7 @@ class AppleAppStoreBridge extends BridgeAbstract
|
||||
return $json['data'];
|
||||
}
|
||||
|
||||
private function getJWTToken($id, $platform, $country)
|
||||
{
|
||||
private function getJWTToken($id, $platform, $country){
|
||||
$uri = $this->makeHtmlUrl($id, $country);
|
||||
|
||||
$html = getSimpleHTMLDOMCached($uri, 3600);
|
||||
@@ -106,14 +89,13 @@ class AppleAppStoreBridge extends BridgeAbstract
|
||||
return $json->MEDIA_API->token;
|
||||
}
|
||||
|
||||
private function getAppData($id, $platform, $country, $token)
|
||||
{
|
||||
private function getAppData($id, $platform, $country, $token){
|
||||
$uri = $this->makeJsonUrl($id, $platform, $country);
|
||||
|
||||
$headers = [
|
||||
$headers = array(
|
||||
"Authorization: Bearer $token",
|
||||
'Origin: https://apps.apple.com',
|
||||
];
|
||||
);
|
||||
|
||||
$json = json_decode(getContents($uri, $headers), true);
|
||||
|
||||
@@ -124,8 +106,7 @@ class AppleAppStoreBridge extends BridgeAbstract
|
||||
* Parses the version history from the data received
|
||||
* @return array list of versions with details on each element
|
||||
*/
|
||||
private function getVersionHistory($data, $platform)
|
||||
{
|
||||
private function getVersionHistory($data, $platform){
|
||||
switch($platform) {
|
||||
case 'mac':
|
||||
return $data['relationships']['platforms']['data'][0]['attributes']['versionHistory'];
|
||||
@@ -135,8 +116,7 @@ class AppleAppStoreBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$id = $this->getInput('id');
|
||||
$country = $this->getInput('country');
|
||||
$platform = $this->getInput('p');
|
||||
@@ -156,7 +136,7 @@ class AppleAppStoreBridge extends BridgeAbstract
|
||||
$author = $data['attributes']['artistName'];
|
||||
|
||||
foreach ($versionHistory as $row) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['content'] = nl2br($row['releaseNotes']);
|
||||
$item['title'] = $name . ' - ' . $row['versionDisplay'];
|
||||
|
@@ -1,27 +1,25 @@
|
||||
<?php
|
||||
|
||||
class AppleMusicBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(array(
|
||||
'artist' => array(
|
||||
'name' => 'Artist ID',
|
||||
'exampleValue' => '909253',
|
||||
'required' => true,
|
||||
],
|
||||
'limit' => [
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Latest X Releases (max 50)',
|
||||
'defaultValue' => '10',
|
||||
'required' => true,
|
||||
],
|
||||
]];
|
||||
),
|
||||
));
|
||||
const CACHE_TIMEOUT = 21600; // 6 hours
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
# Limit the amount of releases to 50
|
||||
if ($this->getInput('limit') > 50) {
|
||||
$limit = 50;
|
||||
@@ -40,9 +38,7 @@ class AppleMusicBridge extends BridgeAbstract
|
||||
|
||||
foreach ($json->results as $obj) {
|
||||
if ($obj->wrapperType === 'collection') {
|
||||
$copyright = $obj->copyright ?? '';
|
||||
|
||||
$this->items[] = [
|
||||
$this->items[] = array(
|
||||
'title' => $obj->artistName . ' - ' . $obj->collectionName,
|
||||
'uri' => $obj->collectionViewUrl,
|
||||
'timestamp' => $obj->releaseDate,
|
||||
@@ -51,8 +47,8 @@ class AppleMusicBridge extends BridgeAbstract
|
||||
. '><img src="' . $obj->artworkUrl100 . '" /></a><br><br>'
|
||||
. $obj->artistName . ' - ' . $obj->collectionName
|
||||
. '<br>'
|
||||
. $copyright,
|
||||
];
|
||||
. $obj->copyright,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,71 +0,0 @@
|
||||
<?php
|
||||
|
||||
class ArsTechnicaBridge extends FeedExpander
|
||||
{
|
||||
const MAINTAINER = 'phantop';
|
||||
const NAME = 'Ars Technica';
|
||||
const URI = 'https://arstechnica.com/';
|
||||
const DESCRIPTION = 'Returns the latest articles from Ars Technica';
|
||||
const PARAMETERS = [[
|
||||
'section' => [
|
||||
'name' => 'Site section',
|
||||
'type' => 'list',
|
||||
'defaultValue' => 'index',
|
||||
'values' => [
|
||||
'All' => 'index',
|
||||
'Apple' => 'apple',
|
||||
'Board Games' => 'cardboard',
|
||||
'Cars' => 'cars',
|
||||
'Features' => 'features',
|
||||
'Gaming' => 'gaming',
|
||||
'Information Technology' => 'technology-lab',
|
||||
'Science' => 'science',
|
||||
'Staff Blogs' => 'staff-blogs',
|
||||
'Tech Policy' => 'tech-policy',
|
||||
'Tech' => 'gadgets',
|
||||
]
|
||||
]
|
||||
]];
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = 'https://feeds.arstechnica.com/arstechnica/' . $this->getInput('section');
|
||||
$this->collectExpandableDatas($url);
|
||||
}
|
||||
|
||||
protected function parseItem($newItem)
|
||||
{
|
||||
$item = parent::parseItem($newItem);
|
||||
|
||||
$item_html = getSimpleHTMLDOMCached($item['uri'] . '&');
|
||||
$item_html = defaultLinkTo($item_html, self::URI);
|
||||
$item['content'] = $item_html->find('.amp-wp-article-content', 0);
|
||||
|
||||
// remove various ars advertising
|
||||
$item['content']->find('#social-left', 0)->remove();
|
||||
foreach ($item['content']->find('.ars-component-buy-box') as $ad) {
|
||||
$ad->remove();
|
||||
}
|
||||
foreach ($item['content']->find('i-amphtml-sizer') as $ad) {
|
||||
$ad->remove();
|
||||
}
|
||||
foreach ($item['content']->find('.sidebar') as $ad) {
|
||||
$ad->remove();
|
||||
}
|
||||
|
||||
foreach ($item['content']->find('a') as $link) { //remove amp redirect links
|
||||
$url = $link->getAttribute('href');
|
||||
if (str_contains($url, 'go.redirectingat.com')) {
|
||||
$url = extractFromDelimiters($url, 'url=', '&');
|
||||
$url = urldecode($url);
|
||||
$link->setAttribute('href', $url);
|
||||
}
|
||||
}
|
||||
|
||||
$item['content'] = backgroundToImg(str_replace('data-amp-original-style="background-image', 'style="background-image', $item['content']));
|
||||
|
||||
$item['uid'] = explode('=', $item['uri'])[1];
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -1,63 +1,56 @@
|
||||
<?php
|
||||
|
||||
class ArtStationBridge extends BridgeAbstract
|
||||
{
|
||||
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 = [
|
||||
'Search Query' => [
|
||||
'q' => [
|
||||
const PARAMETERS = array(
|
||||
'Search Query' => array(
|
||||
'q' => array(
|
||||
'name' => 'Search term',
|
||||
'required' => true,
|
||||
'exampleValue' => 'bird'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return 'https://www.artstation.com/assets/favicon-58653022bc38c1905ac7aa1b10bffa6b.ico';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
return self::NAME . ': ' . $this->getInput('q');
|
||||
}
|
||||
|
||||
private function fetchSearch($searchQuery)
|
||||
{
|
||||
private function fetchSearch($searchQuery) {
|
||||
$data = '{"query":"' . $searchQuery . '","page":1,"per_page":50,"sorting":"date",';
|
||||
$data .= '"pro_first":"1","filters":[],"additional_fields":[]}';
|
||||
|
||||
$header = [
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Accept: application/json'
|
||||
];
|
||||
);
|
||||
|
||||
$opts = [
|
||||
$opts = array(
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $data,
|
||||
CURLOPT_RETURNTRANSFER => true
|
||||
];
|
||||
);
|
||||
|
||||
$jsonSearchURL = self::URI . '/api/v2/search/projects.json';
|
||||
$jsonSearchStr = getContents($jsonSearchURL, $header, $opts);
|
||||
return json_decode($jsonSearchStr);
|
||||
}
|
||||
|
||||
private function fetchProject($hashID)
|
||||
{
|
||||
private function fetchProject($hashID) {
|
||||
$jsonProjectURL = self::URI . '/projects/' . $hashID . '.json';
|
||||
$jsonProjectStr = getContents($jsonProjectURL);
|
||||
return json_decode($jsonProjectStr);
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$searchTerm = $this->getInput('q');
|
||||
$jsonQuery = $this->fetchSearch($searchTerm);
|
||||
|
||||
@@ -66,7 +59,7 @@ class ArtStationBridge extends BridgeAbstract
|
||||
$jsonProject = $this->fetchProject($media->hash_id);
|
||||
|
||||
// create item
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['title'] = $media->title;
|
||||
$item['uri'] = $media->url;
|
||||
$item['timestamp'] = strtotime($jsonProject->published_at);
|
||||
@@ -83,19 +76,17 @@ class ArtStationBridge extends BridgeAbstract
|
||||
|
||||
$numAssets = count($jsonProject->assets);
|
||||
|
||||
if ($numAssets > 1) {
|
||||
if ($numAssets > 1)
|
||||
$item['content'] .= '<p><a href="'
|
||||
. $media->url
|
||||
. '">Project contains '
|
||||
. ($numAssets - 1)
|
||||
. ' more item(s).</a></p>';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<?php
|
||||
class Arte7Bridge extends BridgeAbstract {
|
||||
|
||||
class Arte7Bridge extends BridgeAbstract
|
||||
{
|
||||
// const MAINTAINER = 'mitsukarenai';
|
||||
const NAME = 'Arte +7';
|
||||
const URI = 'https://www.arte.tv/';
|
||||
const CACHE_TIMEOUT = 1800; // 30min
|
||||
@@ -9,60 +9,34 @@ class Arte7Bridge extends BridgeAbstract
|
||||
|
||||
const API_TOKEN = 'Nzc1Yjc1ZjJkYjk1NWFhN2I2MWEwMmRlMzAzNjI5NmU3NWU3ODg4ODJjOWMxNTMxYzEzZGRjYjg2ZGE4MmIwOA';
|
||||
|
||||
const PARAMETERS = [
|
||||
const PARAMETERS = array(
|
||||
'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',
|
||||
'video_duration_filter' => [
|
||||
'name' => 'Exclude short videos',
|
||||
'type' => 'checkbox',
|
||||
'required' => false,
|
||||
'defaultValue' => false
|
||||
'title' => 'Exclude videos that are shorter than 3 minutes',
|
||||
'defaultValue' => false,
|
||||
],
|
||||
],
|
||||
'Category' => [
|
||||
'lang' => [
|
||||
'Category' => array(
|
||||
'lang' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Language',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Français' => 'fr',
|
||||
'Deutsch' => 'de',
|
||||
'English' => 'en',
|
||||
'Español' => 'es',
|
||||
'Polski' => 'pl',
|
||||
'Italiano' => 'it'
|
||||
],
|
||||
],
|
||||
'cat' => [
|
||||
),
|
||||
'title' => 'ex. RC-014095 pour https://www.arte.tv/fr/videos/RC-014095/blow-up/',
|
||||
'exampleValue' => 'RC-014095'
|
||||
),
|
||||
'cat' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Category',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'All videos' => null,
|
||||
'News & society' => 'ACT',
|
||||
'Series & fiction' => 'SER',
|
||||
@@ -73,33 +47,33 @@ class Arte7Bridge extends BridgeAbstract
|
||||
'History' => 'HIST',
|
||||
'Science' => 'SCI',
|
||||
'Other' => 'AUT'
|
||||
]
|
||||
],
|
||||
],
|
||||
'Collection' => [
|
||||
'lang' => [
|
||||
)
|
||||
),
|
||||
),
|
||||
'Collection' => array(
|
||||
'lang' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Language',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Français' => 'fr',
|
||||
'Deutsch' => 'de',
|
||||
'English' => 'en',
|
||||
'Español' => 'es',
|
||||
'Polski' => 'pl',
|
||||
'Italiano' => 'it'
|
||||
]
|
||||
],
|
||||
'col' => [
|
||||
)
|
||||
),
|
||||
'col' => 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()
|
||||
{
|
||||
public function collectData(){
|
||||
$lang = $this->getInput('lang');
|
||||
switch($this->queriedContext) {
|
||||
case 'Category':
|
||||
$category = $this->getInput('cat');
|
||||
@@ -111,40 +85,34 @@ class Arte7Bridge extends BridgeAbstract
|
||||
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?limit=15&language='
|
||||
$url = 'https://api.arte.tv/api/opa/v3/videos?sort=-lastModified&limit=15&language='
|
||||
. $lang
|
||||
. ($sort_by != null ? '&sort=' . $sort_direction . $sort_by : '')
|
||||
. ($category != null ? '&category.code=' . $category : '')
|
||||
. ($collectionId != null ? '&collections.collectionId=' . $collectionId : '');
|
||||
|
||||
$header = [
|
||||
$header = array(
|
||||
'Authorization: Bearer ' . self::API_TOKEN
|
||||
];
|
||||
);
|
||||
|
||||
$input = getContents($url, $header);
|
||||
$input_json = json_decode($input, true);
|
||||
|
||||
foreach($input_json['videos'] as $element) {
|
||||
if ($this->getInput('exclude_trailers') && $element['platform'] == 'EXTRAIT') {
|
||||
$durationSeconds = $element['durationSeconds'];
|
||||
|
||||
if ($this->getInput('video_duration_filter') && $durationSeconds < 60 * 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$durationSeconds = $element['durationSeconds'];
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $element['url'];
|
||||
$item['id'] = $element['id'];
|
||||
|
||||
$item['timestamp'] = strtotime($element['videoRightsBegin']);
|
||||
$item['title'] = $element['title'];
|
||||
|
||||
if (!empty($element['subtitle'])) {
|
||||
if(!empty($element['subtitle']))
|
||||
$item['title'] = $element['title'] . ' | ' . $element['subtitle'];
|
||||
}
|
||||
|
||||
$durationMinutes = round((int)$durationSeconds / 60);
|
||||
$item['content'] = $element['teaserText']
|
||||
|
@@ -1,18 +1,16 @@
|
||||
<?php
|
||||
|
||||
class AsahiShimbunAJWBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'section' => array(
|
||||
'type' => 'list',
|
||||
'name' => 'Section',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Japan » Social Affairs' => 'japan/social',
|
||||
'Japan » People' => 'japan/people',
|
||||
'Japan » 3/11 Disaster' => 'japan/0311disaster',
|
||||
@@ -22,25 +20,22 @@ class AsahiShimbunAJWBridge extends BridgeAbstract
|
||||
'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',
|
||||
'Asia » China' => 'asia/china',
|
||||
'Asia » Korean Peninsula' => 'asia/korean_peninsula',
|
||||
'Asia » Around Asia' => 'asia/around_asia',
|
||||
'Opinion » Editorial' => 'opinion/editorial',
|
||||
'Opinion » Vox Populi' => 'opinion/vox',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'politics',
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function getSectionURI($section)
|
||||
{
|
||||
return $this->getURI() . $section . '/';
|
||||
private function getSectionURI($section) {
|
||||
return self::getURI() . $section . '/';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getSectionURI($this->getInput('section')));
|
||||
|
||||
foreach($html->find('#MainInner li a') as $element) {
|
||||
@@ -48,7 +43,7 @@ class AsahiShimbunAJWBridge extends BridgeAbstract
|
||||
Debug::log('Skip Headline, it is repeated below');
|
||||
continue;
|
||||
}
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = self::BASE_URI . $element->href;
|
||||
$e_lead = $element->find('span.Lead', 0);
|
||||
@@ -68,12 +63,7 @@ class AsahiShimbunAJWBridge extends BridgeAbstract
|
||||
$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;
|
||||
}
|
||||
|
@@ -1,44 +1,40 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array(
|
||||
'Ask.fm username' => array(
|
||||
'u' => array(
|
||||
'name' => 'Username',
|
||||
'required' => true,
|
||||
'exampleValue' => 'ApprovedAndReal'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, self::URI);
|
||||
|
||||
foreach($html->find('article.streamItem-answer') as $element) {
|
||||
$item = [];
|
||||
$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,
|
||||
htmlspecialchars_decode($element->find('header.streamItem_header', 0)->plaintext,
|
||||
ENT_QUOTES
|
||||
)
|
||||
);
|
||||
|
||||
$item['timestamp'] = strtotime($element->find('time', 0)->datetime);
|
||||
|
||||
$var = $element->find('div.streamItem_content', 0);
|
||||
$answer = trim($var->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)) {
|
||||
@@ -60,8 +56,7 @@ class AskfmBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::NAME . ' : ' . $this->getInput('u');
|
||||
}
|
||||
@@ -69,8 +64,7 @@ class AskfmBridge extends BridgeAbstract
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('u'))) {
|
||||
return self::URI . urlencode($this->getInput('u'));
|
||||
}
|
||||
|
@@ -1,17 +1,15 @@
|
||||
<?php
|
||||
|
||||
class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'Standard Topics' => array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'AP Top News' => 'apf-topnews',
|
||||
'Sports' => 'apf-sports',
|
||||
'Entertainment' => 'apf-entertainment',
|
||||
@@ -29,19 +27,19 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
'Photo Galleries' => 'PhotoGalleries',
|
||||
'Fact Checks' => 'APFactCheck',
|
||||
'Videos' => 'apf-videos',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'apf-topnews',
|
||||
],
|
||||
],
|
||||
'Custom Topic' => [
|
||||
'topic' => [
|
||||
),
|
||||
),
|
||||
'Custom Topic' => array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'europe'
|
||||
],
|
||||
]
|
||||
];
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 900; // 15 mins
|
||||
|
||||
@@ -49,9 +47,8 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
private $tagEndpoint = 'https://afs-prod.appspot.com/api/v2/feed/tag?tags=';
|
||||
private $feedName = '';
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
$params = [];
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
if(preg_match($this->detectParamRegex, $url, $matches) > 0) {
|
||||
$params['topic'] = $matches[1];
|
||||
@@ -62,8 +59,7 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
switch($this->getInput('topic')) {
|
||||
case 'Podcasts':
|
||||
returnClientError('Podcasts topic feed is not supported');
|
||||
@@ -76,8 +72,7 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('topic'))) {
|
||||
return self::URI . $this->getInput('topic');
|
||||
}
|
||||
@@ -85,8 +80,7 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
if (!empty($this->feedName)) {
|
||||
return $this->feedName . ' - Associated Press';
|
||||
}
|
||||
@@ -94,8 +88,7 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getTagURI()
|
||||
{
|
||||
private function getTagURI() {
|
||||
if (!is_null($this->getInput('topic'))) {
|
||||
return $this->tagEndpoint . $this->getInput('topic');
|
||||
}
|
||||
@@ -103,8 +96,7 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
private function collectCardData()
|
||||
{
|
||||
private function collectCardData() {
|
||||
$json = getContents($this->getTagURI())
|
||||
or returnServerError('Could not request: ' . $this->getTagURI());
|
||||
|
||||
@@ -117,7 +109,7 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
$this->feedName = $tagContents['tagObjs'][0]['name'];
|
||||
|
||||
foreach ($tagContents['cards'] as $card) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
// skip hub peeks & Notifications
|
||||
if ($card['cardType'] == 'Hub Peek' || $card['cardType'] == 'Notification') {
|
||||
@@ -149,11 +141,8 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
$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['enclosures'][] = 'https://storage.googleapis.com/afs-prod/media/'
|
||||
. $storyContent['leadPhotoId'] . '/800.jpeg';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,8 +178,8 @@ class AssociatedPressNewsBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function processMediaPlaceholders($html, $id)
|
||||
{
|
||||
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);
|
||||
@@ -227,10 +216,11 @@ EOD;
|
||||
/*
|
||||
Create full coverage links (HubLinks)
|
||||
*/
|
||||
private function processHubLinks($html, $storyContent)
|
||||
{
|
||||
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);
|
||||
@@ -245,8 +235,7 @@ EOD;
|
||||
}
|
||||
}
|
||||
|
||||
private function processVideo($storyContent)
|
||||
{
|
||||
private function processVideo($storyContent) {
|
||||
$video = $storyContent['media'][0];
|
||||
|
||||
if ($video['type'] === 'YouTube') {
|
||||
@@ -266,8 +255,8 @@ EOD;
|
||||
}
|
||||
|
||||
// Remove datawrapper.dwcdn.net iframes and related javaScript
|
||||
private function processIframes($html)
|
||||
{
|
||||
private function processIframes($html) {
|
||||
|
||||
foreach ($html->find('iframe') as $index => $iframe) {
|
||||
if (preg_match('/datawrapper\.dwcdn\.net/', $iframe->src)) {
|
||||
$iframe->outertext = '';
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,24 +1,22 @@
|
||||
<?php
|
||||
class AtmoNouvelleAquitaineBridge extends BridgeAbstract {
|
||||
|
||||
class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
{
|
||||
const NAME = 'Atmo Nouvelle Aquitaine';
|
||||
const URI = 'https://www.atmo-nouvelleaquitaine.org';
|
||||
const DESCRIPTION = 'Fetches the latest air polution of cities in Nouvelle Aquitaine from Atmo';
|
||||
const MAINTAINER = 'floviolleau';
|
||||
const PARAMETERS = [[
|
||||
'cities' => [
|
||||
const PARAMETERS = array(array(
|
||||
'cities' => array(
|
||||
'name' => 'Choisir une ville',
|
||||
'type' => 'list',
|
||||
'values' => self::CITIES
|
||||
]
|
||||
]];
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 7200;
|
||||
|
||||
private $dom;
|
||||
|
||||
private function getClosest($search, $arr)
|
||||
{
|
||||
private function getClosest($search, $arr) {
|
||||
$closest = null;
|
||||
foreach ($arr as $key => $value) {
|
||||
if ($closest === null || abs((int)$search - $closest) > abs((int)$key - (int)$search)) {
|
||||
@@ -28,11 +26,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return $arr[$closest];
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// this bridge is broken and unmaintained
|
||||
return;
|
||||
|
||||
public function collectData() {
|
||||
$uri = self::URI . '/monair/commune/' . $this->getInput('cities');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
@@ -53,8 +47,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
private function getIndex()
|
||||
{
|
||||
private function getIndex() {
|
||||
$index = $this->dom->find('.indice', 0)->innertext;
|
||||
|
||||
if ($index == 'XX') {
|
||||
@@ -64,14 +57,12 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return $index;
|
||||
}
|
||||
|
||||
private function getMaxIndexText()
|
||||
{
|
||||
private function getMaxIndexText() {
|
||||
// will return '/100'
|
||||
return $this->dom->find('.pourcent', 0)->innertext;
|
||||
}
|
||||
|
||||
private function getQualityText($index, $indexes)
|
||||
{
|
||||
private function getQualityText($index, $indexes) {
|
||||
if ($index == -1) {
|
||||
if (array_key_exists('no-available', $indexes)) {
|
||||
return $indexes['no-available'];
|
||||
@@ -83,10 +74,9 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return $this->getClosest($index, $indexes);
|
||||
}
|
||||
|
||||
private function getLegendIndexes()
|
||||
{
|
||||
private function getLegendIndexes() {
|
||||
$rawIndexes = $this->dom->find('.prevision-legend .prevision-legend-label');
|
||||
$indexes = [];
|
||||
$indexes = array();
|
||||
for ($i = 0; $i < count($rawIndexes); $i++) {
|
||||
if ($rawIndexes[$i]->hasAttribute('data-color')) {
|
||||
$indexes[$rawIndexes[$i]->getAttribute('data-color')] = $rawIndexes[$i]->innertext;
|
||||
@@ -96,8 +86,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return $indexes;
|
||||
}
|
||||
|
||||
private function getTomorrowTrendIndex()
|
||||
{
|
||||
private function getTomorrowTrendIndex() {
|
||||
$tomorrowTrendDomNode = $this->dom
|
||||
->find('.day-controls.raster-controls .list-raster-controls .raster-control', 2);
|
||||
$tomorrowTrendIndexNode = null;
|
||||
@@ -115,8 +104,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return $tomorrowTrendIndex;
|
||||
}
|
||||
|
||||
private function getTomorrowTrendQualityText($trendIndex, $indexes)
|
||||
{
|
||||
private function getTomorrowTrendQualityText($trendIndex, $indexes) {
|
||||
if ($trendIndex == -1) {
|
||||
if (array_key_exists('no-available', $indexes)) {
|
||||
return $indexes['no-available'];
|
||||
@@ -128,8 +116,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return $this->getClosest($trendIndex, $indexes);
|
||||
}
|
||||
|
||||
private function getIndexMessage()
|
||||
{
|
||||
private function getIndexMessage() {
|
||||
$index = $this->getIndex();
|
||||
$maxIndexText = $this->getMaxIndexText();
|
||||
|
||||
@@ -140,8 +127,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return "L'indice d'aujourd'hui est $index$maxIndexText.";
|
||||
}
|
||||
|
||||
private function getQualityMessage()
|
||||
{
|
||||
private function getQualityMessage() {
|
||||
$index = $index = $this->getIndex();
|
||||
$indexes = $this->getLegendIndexes();
|
||||
$quality = $this->getQualityText($index, $indexes);
|
||||
@@ -153,8 +139,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return "La qualité de l'air est $quality.";
|
||||
}
|
||||
|
||||
private function getTomorrowTrendIndexMessage()
|
||||
{
|
||||
private function getTomorrowTrendIndexMessage() {
|
||||
$trendIndex = $this->getTomorrowTrendIndex();
|
||||
$maxIndexText = $this->getMaxIndexText();
|
||||
|
||||
@@ -165,8 +150,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return "L'indice prévu pour demain est $trendIndex$maxIndexText.";
|
||||
}
|
||||
|
||||
private function getTomorrowTrendQualityMessage()
|
||||
{
|
||||
private function getTomorrowTrendQualityMessage() {
|
||||
$trendIndex = $this->getTomorrowTrendIndex();
|
||||
$indexes = $this->getLegendIndexes();
|
||||
$trendQuality = $this->getTomorrowTrendQualityText($trendIndex, $indexes);
|
||||
@@ -177,7 +161,7 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
return "La qualite de l'air pour demain sera $trendQuality.";
|
||||
}
|
||||
|
||||
const CITIES = [
|
||||
const CITIES = array(
|
||||
'Aast (64460)' => '64001',
|
||||
'Abère (64160)' => '64002',
|
||||
'Abidos (64150)' => '64003',
|
||||
@@ -4649,5 +4633,5 @@ class AtmoNouvelleAquitaineBridge extends BridgeAbstract
|
||||
'Yvrac (33370)' => '33554',
|
||||
'Yvrac-et-Malleyrand (16110)' => '16425',
|
||||
'Yzosse (40180)' => '40334'
|
||||
];
|
||||
);
|
||||
}
|
||||
|
@@ -1,22 +1,20 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array(array(
|
||||
'city' => array(
|
||||
'name' => 'Ville',
|
||||
'required' => true,
|
||||
'exampleValue' => 'cahors'
|
||||
]
|
||||
]];
|
||||
)
|
||||
));
|
||||
const CACHE_TIMEOUT = 7200;
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$uri = self::URI . $this->getInput('city');
|
||||
|
||||
$html = getSimpleHTMLDOM($uri);
|
||||
|
@@ -1,39 +1,29 @@
|
||||
<?php
|
||||
|
||||
class AutoJMBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
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 308'
|
||||
],
|
||||
]
|
||||
];
|
||||
|
||||
'exampleValue' => 'recherche?brands[]=peugeot&ranges[]=peugeot-nouvelle-308-2021-5p'
|
||||
),
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const TEST_DETECT_PARAMETERS = [
|
||||
'https://www.autojm.fr/recherche?brands%5B%5D=PEUGEOT&ranges%5B%5D=PEUGEOT%20308'
|
||||
=> ['url' => 'recherche?brands%5B%5D=PEUGEOT&ranges%5B%5D=PEUGEOT%20308',
|
||||
'context' => 'Afficher les offres de véhicules disponible sur la recheche AutoJM'
|
||||
]
|
||||
];
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return self::URI . 'favicon.ico';
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Afficher les offres de véhicules disponible sur la recheche AutoJM':
|
||||
return 'AutoJM | Recherche de véhicules';
|
||||
@@ -41,28 +31,18 @@ class AutoJMBridge extends BridgeAbstract
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
switch ($this->queriedContext) {
|
||||
case 'Afficher les offres de véhicules disponible sur la recheche AutoJM':
|
||||
return self::URI . $this->getInput('url');
|
||||
break;
|
||||
default:
|
||||
return self::URI;
|
||||
}
|
||||
}
|
||||
public function collectData() {
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
// Get the number of result for this search
|
||||
$search_url = self::URI . $this->getInput('url') . '&open=energy&onlyFilters=false';
|
||||
|
||||
// Set the header 'X-Requested-With' like the website does it
|
||||
$header = [
|
||||
$header = array(
|
||||
'X-Requested-With: XMLHttpRequest'
|
||||
];
|
||||
);
|
||||
|
||||
// Get the JSON content of the form
|
||||
$json = getContents($search_url, $header);
|
||||
@@ -71,7 +51,7 @@ class AutoJMBridge extends BridgeAbstract
|
||||
$data = json_decode($json);
|
||||
|
||||
$nb_results = $data->nbResults;
|
||||
$total_pages = ceil($nb_results / 14);
|
||||
$total_pages = ceil($nb_results / 15);
|
||||
|
||||
// Limit the number of page to analyse to 10
|
||||
for($page = 1; $page <= $total_pages && $page <= 10; $page++) {
|
||||
@@ -81,12 +61,13 @@ class AutoJMBridge extends BridgeAbstract
|
||||
// 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;
|
||||
$availability = $car->find('div[class*=card-car__modalites]', 0)->find('div[class=col]', 0)->plaintext;
|
||||
$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
|
||||
@@ -116,7 +97,7 @@ class AutoJMBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
// Construct the new item
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['title'] = $car_model;
|
||||
$item['content'] = '<p><img style="vertical-align:middle ; padding: 10px" src="' . $image . '" />'
|
||||
. $car_model . '</p>';
|
||||
@@ -151,18 +132,4 @@ class AutoJMBridge extends BridgeAbstract
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
$params = [];
|
||||
$regex = '/^(https?:\/\/)?(www\.|)autojm.fr\/(recherche\?.*|recherche\/[0-9]{1,10}\?.*)$/m';
|
||||
if (preg_match($regex, $url, $matches) > 0) {
|
||||
$url = preg_replace('#(recherche|recherche/[0-9]{1,10})#', 'recherche', $matches[3]);
|
||||
|
||||
$params['url'] = $url;
|
||||
$params['context'] = 'Afficher les offres de véhicules disponible sur la recheche AutoJM';
|
||||
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
class AwwwardsBridge extends BridgeAbstract
|
||||
{
|
||||
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';
|
||||
@@ -12,14 +10,31 @@ class AwwwardsBridge extends BridgeAbstract
|
||||
const SITEURI = 'https://www.awwwards.com/sites/';
|
||||
const ASSETSURI = 'https://assets.awwwards.com/awards/media/cache/thumb_417_299/';
|
||||
|
||||
private $sites = [];
|
||||
private $sites = array();
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function getIcon() {
|
||||
return 'https://www.awwwards.com/favicon.ico';
|
||||
}
|
||||
|
||||
private function fetchSites() {
|
||||
Debug::log('Fetching all sites');
|
||||
$sites = getSimpleHTMLDOM(self::SITESURI);
|
||||
|
||||
Debug::log('Parsing all JSON data');
|
||||
foreach($sites->find('li[data-model]') as $site) {
|
||||
$decode = html_entity_decode($site->attr['data-model'],
|
||||
ENT_QUOTES, 'utf-8');
|
||||
$decode = json_decode($decode, true);
|
||||
$this->sites[] = $decode;
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$this->fetchSites();
|
||||
|
||||
Debug::log('Building RSS feed');
|
||||
foreach($this->sites as $site) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['title'] = $site['title'];
|
||||
$item['timestamp'] = $site['createdAt'];
|
||||
$item['categories'] = $site['tags'];
|
||||
@@ -32,28 +47,8 @@ class AwwwardsBridge extends BridgeAbstract
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
if(count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
return 'https://www.awwwards.com/favicon.ico';
|
||||
}
|
||||
|
||||
private function fetchSites()
|
||||
{
|
||||
$sites = getSimpleHTMLDOM(self::SITESURI);
|
||||
foreach ($sites->find('.grid-sites li') as $li) {
|
||||
$encodedJson = $li->attr['data-collectable-model-value'] ?? null;
|
||||
if (!$encodedJson) {
|
||||
continue;
|
||||
}
|
||||
$json = html_entity_decode($encodedJson, ENT_QUOTES, 'utf-8');
|
||||
$site = Json::decode($json);
|
||||
$this->sites[] = $site;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,33 +1,30 @@
|
||||
<?php
|
||||
|
||||
class BAEBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filtrer par mots clés',
|
||||
'title' => 'Entrez le mot clé à filtrer ici'
|
||||
],
|
||||
'type' => [
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type de recherche',
|
||||
'title' => 'Afficher seuleument un certain type d\'annonce',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Toutes les annonces' => false,
|
||||
'Les embarquements' => 'boat',
|
||||
'Les skippers' => 'skipper',
|
||||
'Les équipiers' => 'crew'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
@@ -36,21 +33,20 @@ class BAEBridge extends BridgeAbstract
|
||||
$detail = $annonce->find('footer a', 0);
|
||||
|
||||
$htmlDetail = getSimpleHTMLDOMCached(parent::getURI() . $detail->href);
|
||||
if (!$htmlDetail) {
|
||||
if (!$htmlDetail)
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $annonce->find('header h2', 0)->plaintext;
|
||||
$item['uri'] = parent::getURI() . $detail->href;
|
||||
|
||||
$content = $htmlDetail->find('article p', 0)->innertext;
|
||||
if (!empty($this->getInput('keyword'))) {
|
||||
$keyword = $this->removeAccents(strtolower($this->getInput('keyword')));
|
||||
$cleanTitle = $this->removeAccents(strtolower($item['title']));
|
||||
$keyword = $this->remove_accents(strtolower($this->getInput('keyword')));
|
||||
$cleanTitle = $this->remove_accents(strtolower($item['title']));
|
||||
if (strpos($cleanTitle, $keyword) === false) {
|
||||
$cleanContent = $this->removeAccents(strtolower($content));
|
||||
$cleanContent = $this->remove_accents(strtolower($content));
|
||||
if (strpos($cleanContent, $keyword) === false) {
|
||||
continue;
|
||||
}
|
||||
@@ -62,14 +58,13 @@ class BAEBridge extends BridgeAbstract
|
||||
$item['content'] = defaultLinkTo($content, parent::getURI());
|
||||
$image = $htmlDetail->find('#zoom', 0);
|
||||
if ($image) {
|
||||
$item['enclosures'] = [parent::getURI() . $image->getAttribute('src')];
|
||||
$item['enclosures'] = array(parent::getURI() . $image->getAttribute('src'));
|
||||
}
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
if (!empty($this->getInput('type'))) {
|
||||
if ($this->getInput('type') == 'boat') {
|
||||
@@ -84,9 +79,8 @@ class BAEBridge extends BridgeAbstract
|
||||
return $uri;
|
||||
}
|
||||
|
||||
private function removeAccents($string)
|
||||
{
|
||||
$chars = [
|
||||
private function remove_accents($string) {
|
||||
$chars = array(
|
||||
// Decompositions for Latin-1 Supplement
|
||||
'ª' => 'a', 'º' => 'o',
|
||||
'À' => 'A', 'Á' => 'A',
|
||||
@@ -260,7 +254,7 @@ class BAEBridge extends BridgeAbstract
|
||||
'Ǚ' => 'U', 'ǚ' => 'u',
|
||||
// grave accent
|
||||
'Ǜ' => 'U', 'ǜ' => 'u',
|
||||
];
|
||||
);
|
||||
|
||||
$string = strtr($string, $chars);
|
||||
|
||||
|
@@ -1,57 +1,55 @@
|
||||
<?php
|
||||
|
||||
class BadDragonBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'Sales' => array(
|
||||
),
|
||||
'Clearance' => array(
|
||||
'ready_made' => array(
|
||||
'name' => 'Ready Made',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'flop' => [
|
||||
),
|
||||
'flop' => array(
|
||||
'name' => 'Flops',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'skus' => [
|
||||
),
|
||||
'skus' => array(
|
||||
'name' => 'Products',
|
||||
'exampleValue' => 'chanceflared, crackers',
|
||||
'title' => 'Comma separated list of product SKUs'
|
||||
],
|
||||
'onesize' => [
|
||||
),
|
||||
'onesize' => array(
|
||||
'name' => 'One-Size',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'mini' => [
|
||||
),
|
||||
'mini' => array(
|
||||
'name' => 'Mini',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'small' => [
|
||||
),
|
||||
'small' => array(
|
||||
'name' => 'Small',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'medium' => [
|
||||
),
|
||||
'medium' => array(
|
||||
'name' => 'Medium',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'large' => [
|
||||
),
|
||||
'large' => array(
|
||||
'name' => 'Large',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'extralarge' => [
|
||||
),
|
||||
'extralarge' => array(
|
||||
'name' => 'Extra Large',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'category' => [
|
||||
),
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'All' => 'all',
|
||||
'Accessories' => 'accessories',
|
||||
'Merchandise' => 'merchandise',
|
||||
@@ -61,50 +59,50 @@ class BadDragonBridge extends BridgeAbstract
|
||||
'Lil\' Squirts' => 'shooter',
|
||||
'Lil\' Vibes' => 'vibrator',
|
||||
'Wearables' => 'wearable'
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'all',
|
||||
],
|
||||
'soft' => [
|
||||
),
|
||||
'soft' => array(
|
||||
'name' => 'Soft Firmness',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'med_firm' => [
|
||||
),
|
||||
'med_firm' => array(
|
||||
'name' => 'Medium Firmness',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'firm' => [
|
||||
),
|
||||
'firm' => array(
|
||||
'name' => 'Firm',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'split' => [
|
||||
),
|
||||
'split' => array(
|
||||
'name' => 'Split Firmness',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'maxprice' => [
|
||||
),
|
||||
'maxprice' => array(
|
||||
'name' => 'Max Price',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'defaultValue' => 300
|
||||
],
|
||||
'minprice' => [
|
||||
),
|
||||
'minprice' => array(
|
||||
'name' => 'Min Price',
|
||||
'type' => 'number',
|
||||
'defaultValue' => 0
|
||||
],
|
||||
'cumtube' => [
|
||||
),
|
||||
'cumtube' => array(
|
||||
'name' => 'Cumtube',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'suctionCup' => [
|
||||
),
|
||||
'suctionCup' => array(
|
||||
'name' => 'Suction Cup',
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'noAccessories' => [
|
||||
),
|
||||
'noAccessories' => array(
|
||||
'name' => 'No Accessories',
|
||||
'type' => 'checkbox'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
/*
|
||||
* This sets index $strFrom (or $strTo if set) in $outArr to 'on' if
|
||||
@@ -124,21 +122,18 @@ class BadDragonBridge extends BridgeAbstract
|
||||
* [flop] => on
|
||||
* )
|
||||
* */
|
||||
private function setParam($inArr, &$outArr, $param, $strFrom, $strTo = null)
|
||||
{
|
||||
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 = [];
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// Sale
|
||||
$regex = '/^(https?:\/\/)?bad-dragon\.com\/sales/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['context'] = 'Sales';
|
||||
return $params;
|
||||
}
|
||||
|
||||
@@ -151,7 +146,7 @@ class BadDragonBridge extends BridgeAbstract
|
||||
$this->setParam($urlParams, $params, 'type', 'flop');
|
||||
|
||||
if(isset($urlParams['skus'])) {
|
||||
$skus = [];
|
||||
$skus = array();
|
||||
foreach($urlParams['skus'] as $sku) {
|
||||
is_string($sku) && $skus[] = $sku;
|
||||
is_array($sku) && $skus[] = $sku[0];
|
||||
@@ -193,7 +188,6 @@ class BadDragonBridge extends BridgeAbstract
|
||||
isset($urlParams['noAccessories'])
|
||||
&& $urlParams['noAccessories'] === '1'
|
||||
&& $params['noAccessories'] = 'on';
|
||||
$params['context'] = 'Clearance';
|
||||
|
||||
return $params;
|
||||
}
|
||||
@@ -201,8 +195,7 @@ class BadDragonBridge extends BridgeAbstract
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Sales':
|
||||
return 'Bad Dragon Sales';
|
||||
@@ -213,8 +206,7 @@ class BadDragonBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Sales':
|
||||
return self::URI . 'sales';
|
||||
@@ -225,14 +217,13 @@ class BadDragonBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Sales':
|
||||
$sales = json_decode(getContents(self::URI . 'api/sales'));
|
||||
|
||||
foreach($sales as $sale) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $sale->title;
|
||||
$item['timestamp'] = strtotime($sale->startDate);
|
||||
@@ -288,7 +279,7 @@ class BadDragonBridge extends BridgeAbstract
|
||||
. 'api/inventory-toy/product-list'));
|
||||
|
||||
foreach($toyData->toys as $toy) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $this->getURI()
|
||||
. '#'
|
||||
@@ -327,12 +318,12 @@ class BadDragonBridge extends BridgeAbstract
|
||||
. ($toy->cumtube ? 'Cumtube' : '')
|
||||
. ($toy->suction_cup || $toy->cumtube ? '' : 'None');
|
||||
// firmness
|
||||
$firmnessTexts = [
|
||||
$firmnessTexts = array(
|
||||
'2' => 'Extra soft',
|
||||
'3' => 'Soft',
|
||||
'5' => 'Medium',
|
||||
'8' => 'Firm'
|
||||
];
|
||||
);
|
||||
$firmnesses = explode('/', $toy->firmness);
|
||||
if(count($firmnesses) === 2) {
|
||||
$content .= '<br /><b>Firmness:</b> '
|
||||
@@ -351,13 +342,13 @@ class BadDragonBridge extends BridgeAbstract
|
||||
$content .= '</p>';
|
||||
$item['content'] = $content;
|
||||
|
||||
$enclosures = [];
|
||||
$enclosures = array();
|
||||
foreach($toy->images as $image) {
|
||||
$enclosures[] = $image->fullFilename;
|
||||
}
|
||||
$item['enclosures'] = $enclosures;
|
||||
|
||||
$categories = [];
|
||||
$categories = array();
|
||||
$categories[] = $toy->sku;
|
||||
$categories[] = $toy->type;
|
||||
$categories[] = $toy->size;
|
||||
@@ -375,8 +366,7 @@ class BadDragonBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function inputToURL($api = false)
|
||||
{
|
||||
private function inputToURL($api = false) {
|
||||
$url = self::URI;
|
||||
$url .= ($api ? 'api/inventory-toys?' : 'shop/clearance?');
|
||||
|
||||
|
@@ -1,106 +1,90 @@
|
||||
<?php
|
||||
|
||||
class BakaUpdatesMangaReleasesBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'By series' => array(
|
||||
'series_id' => array(
|
||||
'name' => 'Series ID',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => '188066'
|
||||
]
|
||||
],
|
||||
'By list' => [
|
||||
'list_id' => [
|
||||
)
|
||||
),
|
||||
'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';
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
if ($this -> queriedContext == 'By series') {
|
||||
public function collectData() {
|
||||
if($this -> queriedContext == 'By series')
|
||||
$this -> collectDataBySeries();
|
||||
} else { //queriedContext == 'By list'
|
||||
else //queriedContext == 'By list'
|
||||
$this -> collectDataByList();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
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'
|
||||
} else //queriedContext == 'By list'
|
||||
return self::RELEASES_URL;
|
||||
}
|
||||
|
||||
return self::URI;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
if(!empty($this->feedName)) {
|
||||
return $this->feedName . ' - ' . self::NAME;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
private function getSanitizedHash($string)
|
||||
{
|
||||
private function getSanitizedHash($string) {
|
||||
return hash('sha1', preg_replace('/[^a-zA-Z0-9\-\.]/', '', ucwords(strtolower($string))));
|
||||
}
|
||||
|
||||
private function filterText($text)
|
||||
{
|
||||
private function filterText($text) {
|
||||
return rtrim($text, '* ');
|
||||
}
|
||||
|
||||
private function filterHTML($text)
|
||||
{
|
||||
private function filterHTML($text) {
|
||||
return $this->filterText(html_entity_decode($text));
|
||||
}
|
||||
|
||||
private function findID($manga)
|
||||
{
|
||||
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 {
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private function collectDataBySeries()
|
||||
{
|
||||
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) {
|
||||
if (!$cols)
|
||||
returnServerError('No releases');
|
||||
}
|
||||
|
||||
$rows = array_slice(
|
||||
array_chunk($cols, self::LIMIT_COLS),
|
||||
0,
|
||||
self::LIMIT_ITEMS
|
||||
array_chunk($cols, self::LIMIT_COLS), 0, self::LIMIT_ITEMS
|
||||
);
|
||||
|
||||
if (isset($rows[0][1])) {
|
||||
@@ -108,19 +92,16 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
foreach($rows as $cols) {
|
||||
if (count($cols) < self::LIMIT_COLS) {
|
||||
continue;
|
||||
}
|
||||
if (count($cols) < self::LIMIT_COLS) continue;
|
||||
|
||||
$item = [];
|
||||
$title = [];
|
||||
$item = array();
|
||||
$title = array();
|
||||
|
||||
$item['content'] = '';
|
||||
|
||||
$objDate = $cols[0];
|
||||
if ($objDate) {
|
||||
if ($objDate)
|
||||
$item['timestamp'] = strtotime($objDate->plaintext);
|
||||
}
|
||||
|
||||
$objTitle = $cols[1];
|
||||
if ($objTitle) {
|
||||
@@ -129,14 +110,12 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
$objVolume = $cols[2];
|
||||
if ($objVolume && !empty($objVolume->plaintext)) {
|
||||
if ($objVolume && !empty($objVolume->plaintext))
|
||||
$title[] = 'Vol.' . $objVolume->plaintext;
|
||||
}
|
||||
|
||||
$objChapter = $cols[3];
|
||||
if ($objChapter && !empty($objChapter->plaintext)) {
|
||||
if ($objChapter && !empty($objChapter->plaintext))
|
||||
$title[] = 'Chp.' . $objChapter->plaintext;
|
||||
}
|
||||
|
||||
$objAuthor = $cols[4];
|
||||
if ($objAuthor && !empty($objAuthor->plaintext)) {
|
||||
@@ -152,10 +131,9 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function collectDataByList()
|
||||
{
|
||||
private function collectDataByList() {
|
||||
$this -> feedName = 'Releases';
|
||||
$list = [];
|
||||
$list = array();
|
||||
|
||||
$releasesHTML = getSimpleHTMLDOM(self::RELEASES_URL);
|
||||
|
||||
@@ -175,12 +153,10 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract
|
||||
foreach($rows as $cols) {
|
||||
//check if current manga is in user's list.
|
||||
$id = $this -> findId($cols[0]);
|
||||
if (!array_search($id, $list)) {
|
||||
continue;
|
||||
}
|
||||
if(!array_search($id, $list)) continue;
|
||||
|
||||
$item = [];
|
||||
$title = [];
|
||||
$item = array();
|
||||
$title = array();
|
||||
|
||||
$item['content'] = '';
|
||||
|
||||
@@ -191,9 +167,8 @@ class BakaUpdatesMangaReleasesBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
$objVolChap = $cols[1];
|
||||
if ($objVolChap && !empty($objVolChap->plaintext)) {
|
||||
if ($objVolChap && !empty($objVolChap->plaintext))
|
||||
$title[] = $this -> filterHTML($objVolChap -> innertext);
|
||||
}
|
||||
|
||||
$objAuthor = $cols[2];
|
||||
if ($objAuthor && !empty($objAuthor->plaintext)) {
|
||||
|
@@ -1,123 +1,120 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array(
|
||||
'By tag' => array(
|
||||
'tag' => array(
|
||||
'name' => 'tag',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => 'hip-hop-rap'
|
||||
]
|
||||
],
|
||||
'By band' => [
|
||||
'band' => [
|
||||
)
|
||||
),
|
||||
'By band' => array(
|
||||
'band' => array(
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the band page URL',
|
||||
'required' => true,
|
||||
'exampleValue' => 'aesoprock'
|
||||
],
|
||||
'type' => [
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'changes'
|
||||
],
|
||||
'limit' => [
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'title' => 'Number of releases to return',
|
||||
'defaultValue' => 5
|
||||
]
|
||||
],
|
||||
'By label' => [
|
||||
'label' => [
|
||||
)
|
||||
),
|
||||
'By label' => array(
|
||||
'label' => array(
|
||||
'name' => 'label',
|
||||
'type' => 'text',
|
||||
'title' => 'label name as seen in the label page URL',
|
||||
'required' => true
|
||||
],
|
||||
'type' => [
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'changes'
|
||||
],
|
||||
'limit' => [
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'limit',
|
||||
'type' => 'number',
|
||||
'title' => 'Number of releases to return',
|
||||
'defaultValue' => 5
|
||||
]
|
||||
],
|
||||
'By album' => [
|
||||
'band' => [
|
||||
)
|
||||
),
|
||||
'By album' => array(
|
||||
'band' => array(
|
||||
'name' => 'band',
|
||||
'type' => 'text',
|
||||
'title' => 'Band name as seen in the album page URL',
|
||||
'required' => true,
|
||||
'exampleValue' => 'aesoprock'
|
||||
],
|
||||
'album' => [
|
||||
),
|
||||
'album' => array(
|
||||
'name' => 'album',
|
||||
'type' => 'text',
|
||||
'title' => 'Album name as seen in the album page URL',
|
||||
'required' => true,
|
||||
'exampleValue' => 'appleseed'
|
||||
],
|
||||
'type' => [
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Articles are',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Releases' => 'releases',
|
||||
'Releases, new one when track list changes' => 'changes',
|
||||
'Individual tracks' => 'tracks'
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'tracks'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
const IMGURI = 'https://f4.bcbits.com/';
|
||||
const IMGSIZE_300PX = 23;
|
||||
const IMGSIZE_700PX = 16;
|
||||
|
||||
private $feedName;
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return 'https://s4.bcbits.com/img/bc_favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
$url = self::URI . 'api/hub/1/dig_deeper';
|
||||
$data = $this->buildRequestJson();
|
||||
$header = [
|
||||
$header = array(
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($data)
|
||||
];
|
||||
$opts = [
|
||||
);
|
||||
$opts = array(
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $data
|
||||
];
|
||||
);
|
||||
$content = getContents($url, $header, $opts);
|
||||
|
||||
$json = json_decode($content);
|
||||
@@ -142,13 +139,13 @@ class BandcampBridge extends BridgeAbstract
|
||||
$small_img = $this->getImageUrl($entry->art_id, self::IMGSIZE_300PX);
|
||||
$img = $this->getImageUrl($entry->art_id, self::IMGSIZE_700PX);
|
||||
|
||||
$item = [
|
||||
$item = array(
|
||||
'uri' => $url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title
|
||||
];
|
||||
);
|
||||
$item['content'] = "<img src='$small_img' /><br/>$full_title";
|
||||
$item['enclosures'] = [$img];
|
||||
$item['enclosures'] = array($img);
|
||||
$this->items[] = $item;
|
||||
}
|
||||
break;
|
||||
@@ -164,18 +161,17 @@ class BandcampBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
$regex = '/band_id=(\d+)/';
|
||||
if (preg_match($regex, $html, $matches) == false) {
|
||||
if(preg_match($regex, $html, $matches) == false)
|
||||
returnServerError('Unable to find band ID on: ' . $this->getURI());
|
||||
}
|
||||
$band_id = $matches[1];
|
||||
|
||||
$tralbums = [];
|
||||
$tralbums = array();
|
||||
switch($this->queriedContext) {
|
||||
case 'By band':
|
||||
case 'By label':
|
||||
$query_data = [
|
||||
$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'));
|
||||
@@ -185,26 +181,25 @@ class BandcampBridge extends BridgeAbstract
|
||||
// 'a' or 't' for albums and individual tracks respectively
|
||||
$tralbum_type = substr($album_basic_data->item_type, 0, 1);
|
||||
|
||||
$query_data = [
|
||||
$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) {
|
||||
if(preg_match($regex, $html, $matches) == false)
|
||||
returnServerError('Unable to find album ID on: ' . $this->getURI());
|
||||
}
|
||||
$album_id = $matches[1];
|
||||
|
||||
$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;
|
||||
@@ -213,11 +208,11 @@ class BandcampBridge extends BridgeAbstract
|
||||
foreach ($tralbums as $tralbum_data) {
|
||||
if ($tralbum_data->type === 'a' && $this->getInput('type') === 'tracks') {
|
||||
foreach ($tralbum_data->tracks as $track) {
|
||||
$query_data = [
|
||||
$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);
|
||||
@@ -230,8 +225,7 @@ class BandcampBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
private function buildTralbumItem($tralbum_data)
|
||||
{
|
||||
private function buildTralbumItem($tralbum_data){
|
||||
$band_data = $tralbum_data->band;
|
||||
|
||||
// Format title like: ARTIST - ALBUM/TRACK (OPTIONAL RELEASER)
|
||||
@@ -256,15 +250,15 @@ class BandcampBridge extends BridgeAbstract
|
||||
$small_img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_300PX);
|
||||
$img = $this->getImageUrl($tralbum_data->art_id, self::IMGSIZE_700PX);
|
||||
|
||||
$item = [
|
||||
$item = array(
|
||||
'uri' => $tralbum_data->bandcamp_url,
|
||||
'author' => $full_artist,
|
||||
'title' => $full_title,
|
||||
'enclosures' => [$img],
|
||||
'enclosures' => array($img),
|
||||
'timestamp' => $tralbum_data->release_date
|
||||
];
|
||||
);
|
||||
|
||||
$item['categories'] = [];
|
||||
$item['categories'] = array();
|
||||
foreach ($tralbum_data->tags as $tag) {
|
||||
$item['categories'][] = $tag->norm_name;
|
||||
}
|
||||
@@ -295,31 +289,26 @@ class BandcampBridge extends BridgeAbstract
|
||||
return $item;
|
||||
}
|
||||
|
||||
private function buildRequestJson()
|
||||
{
|
||||
$requestJson = [
|
||||
private function buildRequestJson(){
|
||||
$requestJson = array(
|
||||
'tag' => $this->getInput('tag'),
|
||||
'page' => 1,
|
||||
'sort' => 'date'
|
||||
];
|
||||
);
|
||||
return json_encode($requestJson);
|
||||
}
|
||||
|
||||
private function getImageUrl($id, $size)
|
||||
{
|
||||
private function getImageUrl($id, $size){
|
||||
return self::IMGURI . 'img/a' . $id . '_' . $size . '.jpg';
|
||||
}
|
||||
|
||||
private function apiGet($endpoint, $query_data)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
@@ -356,8 +345,7 @@ class BandcampBridge extends BridgeAbstract
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
switch($this->queriedContext) {
|
||||
case 'By tag':
|
||||
if(!is_null($this->getInput('tag'))) {
|
||||
@@ -390,14 +378,12 @@ class BandcampBridge extends BridgeAbstract
|
||||
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['context'] = 'By tag';
|
||||
$params['tag'] = urldecode($matches[2]);
|
||||
return $params;
|
||||
}
|
||||
@@ -405,7 +391,6 @@ class BandcampBridge extends BridgeAbstract
|
||||
// By band
|
||||
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['context'] = 'By band';
|
||||
$params['band'] = urldecode($matches[2]);
|
||||
return $params;
|
||||
}
|
||||
@@ -413,7 +398,6 @@ class BandcampBridge extends BridgeAbstract
|
||||
// By album
|
||||
$regex = '/^(https?:\/\/)?([^\/.&?\n]+?)\.bandcamp\.com\/album\/([^\/.&?\n]+)/';
|
||||
if(preg_match($regex, $url, $matches) > 0) {
|
||||
$params['context'] = 'By album';
|
||||
$params['band'] = urldecode($matches[2]);
|
||||
$params['album'] = urldecode($matches[3]);
|
||||
return $params;
|
||||
|
@@ -1,18 +1,16 @@
|
||||
<?php
|
||||
|
||||
class BandcampDailyBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'Latest articles' => array(),
|
||||
'Best of' => array(
|
||||
'content' => array(
|
||||
'name' => 'content',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Best Ambient' => 'best-ambient',
|
||||
'Best Beat Tapes' => 'best-beat-tapes',
|
||||
'Best Dance 12\'s' => 'best-dance-12s',
|
||||
@@ -25,15 +23,15 @@ class BandcampDailyBridge extends BridgeAbstract
|
||||
'Best Punk' => 'best-punk',
|
||||
'Best Reissues' => 'best-reissues',
|
||||
'Best Soul' => 'best-soul',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'best-ambient',
|
||||
],
|
||||
],
|
||||
'Genres' => [
|
||||
'genres-content' => [
|
||||
),
|
||||
),
|
||||
'Genres' => array(
|
||||
'content' => array(
|
||||
'name' => 'content',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Acoustic' => 'genres/acoustic',
|
||||
'Alternative' => 'genres/alternative',
|
||||
'Ambient' => 'genres/ambient',
|
||||
@@ -59,15 +57,15 @@ class BandcampDailyBridge extends BridgeAbstract
|
||||
'Soundtrack' => 'genres/soundtrack',
|
||||
'Spoken Word' => 'genres/spoken-word',
|
||||
'World' => 'genres/world',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'genres/acoustic',
|
||||
],
|
||||
],
|
||||
'Franchises' => [
|
||||
'franchises-content' => [
|
||||
),
|
||||
),
|
||||
'Franchises' => array(
|
||||
'content' => array(
|
||||
'name' => 'content',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Lists' => 'lists',
|
||||
'Features' => 'features',
|
||||
'Album of the Day' => 'album-of-the-day',
|
||||
@@ -83,16 +81,15 @@ class BandcampDailyBridge extends BridgeAbstract
|
||||
'Scene Report' => 'scene-report',
|
||||
'Seven Essential Releases' => 'seven-essential-releases',
|
||||
'The Merch Table' => 'the-merch-table',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'lists',
|
||||
],
|
||||
]
|
||||
];
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI())
|
||||
or returnServerError('Could not request: ' . $this->getURI());
|
||||
|
||||
@@ -101,7 +98,7 @@ class BandcampDailyBridge extends BridgeAbstract
|
||||
$articles = $html->find('articles-list', 0);
|
||||
|
||||
foreach($articles->find('div.list-article') as $index => $article) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$articlePath = $article->find('a.title', 0)->href;
|
||||
|
||||
@@ -129,36 +126,30 @@ class BandcampDailyBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
public function 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);
|
||||
return self::URI . '/' . $this->getInput('content');
|
||||
default:
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
switch ($this->queriedContext) {
|
||||
case 'Latest articles':
|
||||
public function getName() {
|
||||
if ($this->queriedContext === '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('content')] . ' - Bandcamp Daily';
|
||||
}
|
||||
|
||||
return $contentValues[$this->getInput($contentKey)] . ' - Bandcamp Daily';
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,20 @@
|
||||
<?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.';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI . 'spip.php?page=backend');
|
||||
|
||||
$limit = 0;
|
||||
|
||||
foreach($html->find('item') as $element) {
|
||||
if($limit < 10) {
|
||||
$item = [];
|
||||
$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);
|
||||
|
@@ -1,55 +1,41 @@
|
||||
<?php
|
||||
|
||||
class BinanceBridge extends BridgeAbstract
|
||||
{
|
||||
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
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return 'https://bin.bnbstatic.com/static/images/common/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM(self::URI)
|
||||
or returnServerError('Could not fetch Binance blog data.');
|
||||
|
||||
$appData = $html->find('script[id="__APP_DATA"]');
|
||||
$appDataJson = json_decode($appData[0]->innertext);
|
||||
$allposts = $appDataJson->routeProps->f3ac->blogListRes->list;
|
||||
|
||||
foreach ($allposts as $element) {
|
||||
$date = $element->releasedTime;
|
||||
foreach($appDataJson->pageData->redux->blogList->blogList as $element) {
|
||||
|
||||
$date = $element->postTime;
|
||||
$abstract = $element->brief;
|
||||
$uri = self::URI . '/' . $element->lang . '/blog/' . $element->idStr;
|
||||
$title = $element->title;
|
||||
$category = $element->category->name;
|
||||
$content = $element->content;
|
||||
|
||||
$suburl = strtolower($category);
|
||||
$suburl = str_replace(' ', '_', $suburl);
|
||||
|
||||
$uri = self::URI . '/' . $suburl . '/' . $element->idStr;
|
||||
|
||||
$contentHTML = getSimpleHTMLDOMCached($uri);
|
||||
$contentAppData = $contentHTML->find('script[id="__APP_DATA"]');
|
||||
$contentAppDataJson = json_decode($contentAppData[0]->innertext);
|
||||
$content = $contentAppDataJson->routeProps->a106->blogDetail->content;
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['title'] = $title;
|
||||
$item['uri'] = $uri;
|
||||
$item['timestamp'] = substr($date, 0, -3);
|
||||
$item['author'] = 'Binance';
|
||||
$item['content'] = $content;
|
||||
$item['categories'] = $category;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 10) {
|
||||
if (count($this->items) >= 10)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,24 +1,23 @@
|
||||
<?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';
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return self::URI . 'assets/img/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
foreach($html->find('div.blague') as $element) {
|
||||
$item = [];
|
||||
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = static::URI . '#' . $element->id;
|
||||
$item['author'] = $element->find('div[class="blague-footer"] p strong', 0)->plaintext;
|
||||
@@ -39,6 +38,8 @@ class BlaguesDeMerdeBridge extends BridgeAbstract
|
||||
$item['timestamp'] = strtotime($matches[1]);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,12 @@
|
||||
<?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.';
|
||||
|
||||
protected function parseItem($item)
|
||||
{
|
||||
protected function parseItem($item){
|
||||
$item = parent::parseItem($item);
|
||||
|
||||
$article_html = getSimpleHTMLDOMCached($item['uri']);
|
||||
@@ -24,8 +22,7 @@ class BleepingComputerBridge extends FeedExpander
|
||||
return $item;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$feed = static::URI . 'feed/';
|
||||
$this->collectExpandableDatas($feed);
|
||||
}
|
||||
|
@@ -1,17 +1,17 @@
|
||||
<?php
|
||||
|
||||
class BlizzardNewsBridge extends XPathAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'' => array(
|
||||
'locale' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Deutsch' => 'de-de',
|
||||
'English (EU)' => 'en-gb',
|
||||
'English (US)' => 'en-us',
|
||||
@@ -27,12 +27,12 @@ class BlizzardNewsBridge extends XPathAbstract
|
||||
'ภาษาไทย' => 'th-th',
|
||||
'简体中文' => 'zh-cn',
|
||||
'繁體中文' => 'zh-tw'
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'en-us',
|
||||
'title' => 'Select your language'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
|
||||
const XPATH_EXPRESSION_ITEM = '/html/body/div/div[4]/div[2]/div[2]/div/div/section/ol/li/article';
|
||||
@@ -49,8 +49,8 @@ class BlizzardNewsBridge extends XPathAbstract
|
||||
* Source Web page URL (should provide either HTML or XML content)
|
||||
* @return string
|
||||
*/
|
||||
protected function getSourceUrl()
|
||||
{
|
||||
protected function getSourceUrl(){
|
||||
|
||||
$locale = $this->getInput('locale');
|
||||
if('zh-cn' === $locale) {
|
||||
return 'https://cn.news.blizzard.com';
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,64 +1,60 @@
|
||||
<?php
|
||||
require_once('DanbooruBridge.php');
|
||||
|
||||
class BooruprojectBridge extends DanbooruBridge {
|
||||
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'global' => array(
|
||||
'p' => array(
|
||||
'name' => 'page',
|
||||
'defaultValue' => 0,
|
||||
'type' => 'number'
|
||||
],
|
||||
't' => [
|
||||
),
|
||||
't' => array(
|
||||
'name' => 'tags',
|
||||
'required' => true,
|
||||
'exampleValue' => 'tagme',
|
||||
'title' => 'Use "all" to get all posts'
|
||||
]
|
||||
],
|
||||
'Booru subdomain (subdomain.booru.org)' => [
|
||||
'i' => [
|
||||
)
|
||||
),
|
||||
'Booru subdomain (subdomain.booru.org)' => array(
|
||||
'i' => array(
|
||||
'name' => 'Subdomain',
|
||||
'required' => true,
|
||||
'exampleValue' => 'rm'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const PATHTODATA = '.thumb';
|
||||
const IDATTRIBUTE = 'id';
|
||||
const TAGATTRIBUTE = 'title';
|
||||
const PIDBYPAGE = 20;
|
||||
|
||||
protected function getFullURI()
|
||||
{
|
||||
protected function getFullURI(){
|
||||
return $this->getURI()
|
||||
. 'index.php?page=post&s=list&pid='
|
||||
. ($this->getInput('p') ? ($this->getInput('p') - 1) * static::PIDBYPAGE : '')
|
||||
. '&tags=' . urlencode($this->getInput('t'));
|
||||
}
|
||||
|
||||
protected function getTags($element)
|
||||
{
|
||||
protected function getTags($element){
|
||||
$tags = parent::getTags($element);
|
||||
$tags = explode(' ', $tags);
|
||||
|
||||
// Remove statistics from the tags list (identified by colon)
|
||||
foreach($tags as $key => $tag) {
|
||||
if (strpos($tag, ':') !== false) {
|
||||
unset($tags[$key]);
|
||||
}
|
||||
if(strpos($tag, ':') !== false) unset($tags[$key]);
|
||||
}
|
||||
|
||||
return implode(' ', $tags);
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('i'))) {
|
||||
return 'https://' . $this->getInput('i') . '.booru.org/';
|
||||
}
|
||||
@@ -66,8 +62,7 @@ class BooruprojectBridge extends DanbooruBridge
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('i'))) {
|
||||
return static::NAME . ' ' . $this->getInput('i');
|
||||
}
|
||||
|
@@ -1,16 +1,14 @@
|
||||
<?php
|
||||
|
||||
class BrutBridge extends BridgeAbstract
|
||||
{
|
||||
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 DESCRIPTION = 'Returns 5 newest videos by category and edition';
|
||||
const MAINTAINER = 'VerifiedJoseph';
|
||||
const PARAMETERS = [[
|
||||
'category' => [
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'News' => 'news',
|
||||
'International' => 'international',
|
||||
'Economy' => 'economy',
|
||||
@@ -19,55 +17,141 @@ class BrutBridge extends BridgeAbstract
|
||||
'Sports' => 'sport',
|
||||
'Nature' => 'nature',
|
||||
'Health' => 'health',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'news',
|
||||
],
|
||||
'edition' => [
|
||||
),
|
||||
'edition' => array(
|
||||
'name' => ' Edition',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'United States' => 'us',
|
||||
'United Kingdom' => 'uk',
|
||||
'France' => 'fr',
|
||||
'Spain' => 'es',
|
||||
'India' => 'in',
|
||||
'Mexico' => 'mx',
|
||||
],
|
||||
),
|
||||
'defaultValue' => 'us',
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
$regex = '/window.__PRELOADED_STATE__ = (.*);/';
|
||||
preg_match($regex, $html, $parts);
|
||||
$data = Json::decode($parts[1], false);
|
||||
foreach ($data->medias->index as $uid => $media) {
|
||||
$this->items[] = [
|
||||
'uid' => $uid,
|
||||
'title' => $media->metadata->slug,
|
||||
'uri' => $media->share_url,
|
||||
'timestamp' => $media->published_at,
|
||||
];
|
||||
const CACHE_TIMEOUT = 1800; // 30 mins
|
||||
|
||||
private $videoId = '';
|
||||
private $videoType = '';
|
||||
private $videoImage = '';
|
||||
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$results = $html->find('div.results', 0);
|
||||
|
||||
foreach($results->find('li.col-6.col-sm-4.col-md-3.col-lg-2.px-2.pb-4') as $index => $li) {
|
||||
$item = array();
|
||||
|
||||
$videoPath = self::URI . $li->children(0)->href;
|
||||
|
||||
$videoPageHtml = getSimpleHTMLDOMCached($videoPath, 3600);
|
||||
|
||||
$this->videoImage = $videoPageHtml->find('meta[name="twitter:image"]', 0)->content;
|
||||
|
||||
$this->processTwitterImage();
|
||||
|
||||
$description = $videoPageHtml->find('div.description', 0);
|
||||
|
||||
$item['uri'] = $videoPath;
|
||||
$item['title'] = $description->find('h1', 0)->plaintext;
|
||||
|
||||
if ($description->find('div.date', 0)->children(0)) {
|
||||
$description->find('div.date', 0)->children(0)->outertext = '';
|
||||
}
|
||||
|
||||
$item['content'] = $this->processContent(
|
||||
$description
|
||||
);
|
||||
|
||||
$item['timestamp'] = $this->processDate($description);
|
||||
$item['enclosures'][] = $this->videoImage;
|
||||
|
||||
$this->items[] = $item;
|
||||
|
||||
if (count($this->items) >= 5) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI() {
|
||||
|
||||
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
|
||||
return self::URI . '/' . $this->getInput('edition') . '/' . $this->getInput('category');
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
|
||||
if (!is_null($this->getInput('edition')) && !is_null($this->getInput('category'))) {
|
||||
return $this->getKey('category') . ' - ' . $this->getKey('edition') . ' - Brut.';
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
@@ -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')) {
|
||||
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);
|
||||
}
|
||||
}
|
@@ -6,13 +6,13 @@ class BukowskisBridge extends BridgeAbstract
|
||||
const URI = 'https://www.bukowskis.com';
|
||||
const DESCRIPTION = 'Fetches info about auction objects from Bukowskis auction house';
|
||||
const MAINTAINER = 'Qluxzz';
|
||||
const PARAMETERS = [[
|
||||
'category' => [
|
||||
const PARAMETERS = array(array(
|
||||
'category' => array(
|
||||
'name' => 'Category',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'All categories' => '',
|
||||
'Art' => [
|
||||
'Art' => array(
|
||||
'All' => 'art',
|
||||
'Classic Art' => 'art.classic-art',
|
||||
'Classic Finnish Art' => 'art.classic-finnish-art',
|
||||
@@ -27,31 +27,31 @@ class BukowskisBridge extends BridgeAbstract
|
||||
'Prints' => 'art.prints',
|
||||
'Sculpture' => 'art.sculpture',
|
||||
'Swedish Old Masters' => 'art.swedish-old-masters',
|
||||
],
|
||||
'Asian Ceramics & Works of Art' => [
|
||||
),
|
||||
'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' => [
|
||||
),
|
||||
'Books & Manuscripts' => array(
|
||||
'All' => 'books-manuscripts',
|
||||
'Books' => 'books-manuscripts.books',
|
||||
],
|
||||
'Carpets, rugs & textiles' => [
|
||||
),
|
||||
'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' => [
|
||||
),
|
||||
'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' => [
|
||||
),
|
||||
'Collectibles' => array(
|
||||
'All' => 'collectibles',
|
||||
'Advertising & Retail' => 'collectibles.advertising-retail',
|
||||
'Memorabilia' => 'collectibles.memorabilia',
|
||||
@@ -60,18 +60,18 @@ class BukowskisBridge extends BridgeAbstract
|
||||
'Retro & Popular Culture' => 'collectibles.retro-popular-culture',
|
||||
'Technica & Nautica' => 'collectibles.technica-nautica',
|
||||
'Toys' => 'collectibles.toys',
|
||||
],
|
||||
'Design' => [
|
||||
),
|
||||
'Design' => array(
|
||||
'All' => 'design',
|
||||
'Art glass' => 'design.art-glass',
|
||||
'Furniture' => 'design.furniture',
|
||||
'Other' => 'design.other',
|
||||
],
|
||||
'Folk art' => [
|
||||
),
|
||||
'Folk art' => array(
|
||||
'All' => 'folk-art',
|
||||
'All categories' => 'lots',
|
||||
],
|
||||
'Furniture' => [
|
||||
),
|
||||
'Furniture' => array(
|
||||
'All' => 'furniture',
|
||||
'Armchairs & Sofas' => 'furniture.armchairs-sofas',
|
||||
'Cabinets & Bureaus' => 'furniture.cabinets-bureaus',
|
||||
@@ -81,13 +81,13 @@ class BukowskisBridge extends BridgeAbstract
|
||||
'Other' => 'furniture.other',
|
||||
'Shelves & Book cases' => 'furniture.shelves-book-cases',
|
||||
'Tables' => 'furniture.tables',
|
||||
],
|
||||
'Glassware' => [
|
||||
),
|
||||
'Glassware' => array(
|
||||
'All' => 'glassware',
|
||||
'Glassware' => 'glassware.glassware',
|
||||
'Other' => 'glassware.other',
|
||||
],
|
||||
'Jewellery' => [
|
||||
),
|
||||
'Jewellery' => array(
|
||||
'All' => 'jewellery',
|
||||
'Bracelets' => 'jewellery.bracelets',
|
||||
'Brooches' => 'jewellery.brooches',
|
||||
@@ -95,8 +95,8 @@ class BukowskisBridge extends BridgeAbstract
|
||||
'Necklaces & Pendants' => 'jewellery.necklaces-pendants',
|
||||
'Other' => 'jewellery.other',
|
||||
'Rings' => 'jewellery.rings',
|
||||
],
|
||||
'Lighting' => [
|
||||
),
|
||||
'Lighting' => array(
|
||||
'All' => 'lighting',
|
||||
'Candle sticks & Candelabras' => 'lighting.candle-sticks-candelabras',
|
||||
'Ceiling lights' => 'lighting.ceiling-lights',
|
||||
@@ -105,46 +105,46 @@ class BukowskisBridge extends BridgeAbstract
|
||||
'Other' => 'lighting.other',
|
||||
'Table lights' => 'lighting.table-lights',
|
||||
'Wall lights' => 'lighting.wall-lights',
|
||||
],
|
||||
'Militaria' => [
|
||||
),
|
||||
'Militaria' => array(
|
||||
'All' => 'militaria',
|
||||
'Honors & Medals' => 'militaria.honors-medals',
|
||||
'Other militaria' => 'militaria.other-militaria',
|
||||
'Weaponry' => 'militaria.weaponry',
|
||||
],
|
||||
'Miscellaneous' => [
|
||||
),
|
||||
'Miscellaneous' => array(
|
||||
'All' => 'miscellaneous',
|
||||
'Brass, Copper & Pewter' => 'miscellaneous.brass-copper-pewter',
|
||||
'Nickel silver' => 'miscellaneous.nickel-silver',
|
||||
'Oriental' => 'miscellaneous.oriental',
|
||||
'Other' => 'miscellaneous.other',
|
||||
],
|
||||
'Silver' => [
|
||||
),
|
||||
'Silver' => array(
|
||||
'All' => 'silver',
|
||||
'Candle sticks' => 'silver.candle-sticks',
|
||||
'Cups & Bowls' => 'silver.cups-bowls',
|
||||
'Cutlery' => 'silver.cutlery',
|
||||
'Other' => 'silver.other',
|
||||
],
|
||||
'Timepieces' => [
|
||||
),
|
||||
'Timepieces' => array(
|
||||
'All' => 'timepieces',
|
||||
'Other' => 'timepieces.other',
|
||||
'Pocket watches' => 'timepieces.pocket-watches',
|
||||
'Table clocks' => 'timepieces.table-clocks',
|
||||
'Wrist watches' => 'timepieces.wrist-watches',
|
||||
],
|
||||
'Vintage & Fashion' => [
|
||||
),
|
||||
'Vintage & Fashion' => array(
|
||||
'All' => 'vintage-fashion',
|
||||
'Accessories' => 'vintage-fashion.accessories',
|
||||
'Bags & Trunks' => 'vintage-fashion.bags-trunks',
|
||||
'Clothes' => 'vintage-fashion.clothes',
|
||||
],
|
||||
]
|
||||
],
|
||||
'sort_order' => [
|
||||
),
|
||||
)
|
||||
),
|
||||
'sort_order' => array(
|
||||
'name' => 'Sort order',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Ending soon' => 'ending',
|
||||
'Most recent' => 'recent',
|
||||
'Most bids' => 'most',
|
||||
@@ -154,18 +154,18 @@ class BukowskisBridge extends BridgeAbstract
|
||||
'Lowest estimate' => 'low',
|
||||
'Highest estimate' => 'high',
|
||||
'Alphabetical' => 'alphabetical',
|
||||
],
|
||||
],
|
||||
'language' => [
|
||||
),
|
||||
),
|
||||
'language' => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'English' => 'en',
|
||||
'Swedish' => 'sv',
|
||||
'Finnish' => 'fi'
|
||||
],
|
||||
],
|
||||
]];
|
||||
),
|
||||
),
|
||||
));
|
||||
|
||||
const CACHE_TIMEOUT = 3600; // 1 hour
|
||||
|
||||
@@ -180,13 +180,11 @@ class BukowskisBridge extends BridgeAbstract
|
||||
|
||||
$url = $baseUrl . '/' . $language . '/lots';
|
||||
|
||||
if ($category) {
|
||||
if ($category)
|
||||
$url = $url . '/category/' . $category;
|
||||
}
|
||||
|
||||
if ($sort_order) {
|
||||
if ($sort_order)
|
||||
$url = $url . '/sort/' . $sort_order;
|
||||
}
|
||||
|
||||
$html = getSimpleHTMLDOM($url);
|
||||
|
||||
@@ -203,13 +201,13 @@ class BukowskisBridge extends BridgeAbstract
|
||||
)
|
||||
);
|
||||
|
||||
$this->items[] = [
|
||||
$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),
|
||||
];
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
<?php
|
||||
class BundesbankBridge extends BridgeAbstract {
|
||||
|
||||
class BundesbankBridge extends BridgeAbstract
|
||||
{
|
||||
const PARAM_LANG = 'lang';
|
||||
|
||||
const LANG_EN = 'en';
|
||||
@@ -13,45 +12,41 @@ class BundesbankBridge extends BridgeAbstract
|
||||
const MAINTAINER = 'logmanoriginal';
|
||||
const CACHE_TIMEOUT = 86400; // 24 hours
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
self::PARAM_LANG => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
self::PARAM_LANG => array(
|
||||
'name' => 'Language',
|
||||
'type' => 'list',
|
||||
'defaultValue' => self::LANG_DE,
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'English' => self::LANG_EN,
|
||||
'Deutsch' => self::LANG_DE
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return self::URI . 'resource/crblob/1890/a7f48ee0ae35348748121770ba3ca009/mL/favicon-ico-data.ico';
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
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';
|
||||
case self::LANG_EN: return self::URI . 'en/publications/reports/studies';
|
||||
case self::LANG_DE: return self::URI . 'de/publikationen/berichte/studien';
|
||||
}
|
||||
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
foreach($html->find('ul.resultlist li') as $study) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['uri'] = $study->find('.teasable__link', 0)->href;
|
||||
|
||||
@@ -71,20 +66,19 @@ class BundesbankBridge extends BridgeAbstract
|
||||
$item['content'] .= '<strong>' . $study->find('.teasable__subtitle', 0)->plaintext . '</strong>';
|
||||
}
|
||||
|
||||
$teasable = $study->find('.teasable__text', 0);
|
||||
$teasableText = $teasable->plaintext ?? '';
|
||||
$item['content'] .= '<p>' . $teasableText . '</p>';
|
||||
$item['content'] .= '<p>' . $study->find('.teasable__text', 0)->plaintext . '</p>';
|
||||
|
||||
$item['timestamp'] = strtotime($study->find('.teasable__date', 0)->plaintext);
|
||||
|
||||
// Downloads and older studies don't have images
|
||||
if($study->find('.teasable__image', 0)) {
|
||||
$item['enclosures'] = [
|
||||
$item['enclosures'] = array(
|
||||
$study->find('.teasable__image img', 0)->src
|
||||
];
|
||||
);
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,5 @@
|
||||
<?php
|
||||
|
||||
class BundestagParteispendenBridge extends BridgeAbstract
|
||||
{
|
||||
class BundestagParteispendenBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'mibe';
|
||||
const NAME = 'Deutscher Bundestag - Parteispenden';
|
||||
const URI = 'https://www.bundestag.de/parlament/praesidium/parteienfinanzierung/fundstellen50000';
|
||||
@@ -54,11 +52,10 @@ URI;
|
||||
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) {
|
||||
if(count($row->children) != 5)
|
||||
return null;
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
// | column | paragraph inside column
|
||||
$party = $row->children[0]->children[0]->innertext;
|
||||
@@ -72,22 +69,20 @@ URI;
|
||||
|
||||
$content = sprintf(self::CONTENT_TEMPLATE, $party, $amount, $donor, $date);
|
||||
|
||||
$item = [
|
||||
$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];
|
||||
}
|
||||
if ($dip != null)
|
||||
$item['enclosures'] = array($dip->href);
|
||||
|
||||
// Try to parse the date
|
||||
$dateTime = DateTime::createFromFormat('d.m.Y', $date);
|
||||
if ($dateTime !== false) {
|
||||
if ($dateTime !== false)
|
||||
$item['timestamp'] = $dateTime->getTimestamp();
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
@@ -1,14 +1,12 @@
|
||||
<?php
|
||||
class CBCEditorsBlogBridge extends BridgeAbstract {
|
||||
|
||||
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()
|
||||
{
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
// Loop on each blog post entry
|
||||
@@ -21,7 +19,7 @@ class CBCEditorsBlogBridge extends BridgeAbstract
|
||||
$thumbnailUri = rtrim(explode(',', $thumbnailUris)[0], ' 300w');
|
||||
|
||||
// Fill item
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $articleUri;
|
||||
$item['id'] = $item['uri'];
|
||||
$item['timestamp'] = $timestamp;
|
||||
|
@@ -1,18 +1,17 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'topic' => array(
|
||||
'name' => 'Topic',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'All articles' => '',
|
||||
'Apple' => 'apple',
|
||||
'Google' => 'google',
|
||||
@@ -23,13 +22,12 @@ class CNETBridge extends BridgeAbstract
|
||||
'Security' => 'topics-security',
|
||||
'Internet' => 'topics-internet',
|
||||
'Tech Industry' => 'topics-tech-industry'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function cleanArticle($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);
|
||||
@@ -46,13 +44,12 @@ class CNETBridge extends BridgeAbstract
|
||||
return $article_html;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
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)))) {
|
||||
if (!empty($topic) && (substr_count($topic, '/') > 1 || !ctype_alpha(str_replace('/', '', $topic))))
|
||||
returnClientError('Invalid topic: ' . $topic);
|
||||
}
|
||||
|
||||
// Retrieve webpage
|
||||
$pageUrl = self::URI . (empty($topic) ? 'news/' : $topic . '/');
|
||||
@@ -60,6 +57,7 @@ class CNETBridge extends BridgeAbstract
|
||||
|
||||
// Process articles
|
||||
foreach($html->find('div.assetBody, div.riverPost') as $element) {
|
||||
|
||||
if(count($this->items) >= 10) {
|
||||
break;
|
||||
}
|
||||
@@ -71,41 +69,37 @@ class CNETBridge extends BridgeAbstract
|
||||
$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_thumbnail)) {
|
||||
if (is_null($article_thumbnail))
|
||||
$article_thumbnail = extractFromDelimiters($element->innertext, '<img src="', '"');
|
||||
}
|
||||
|
||||
if (!empty($article_title) && !empty($article_uri) && strpos($article_uri, self::URI . 'news/') !== false) {
|
||||
|
||||
$article_html = getSimpleHTMLDOMCached($article_uri) or $article_html = null;
|
||||
|
||||
if (!is_null($article_html)) {
|
||||
if (empty($article_thumbnail)) {
|
||||
|
||||
if (empty($article_thumbnail))
|
||||
$article_thumbnail = $article_html->find('div.originalImage', 0);
|
||||
}
|
||||
if (empty($article_thumbnail)) {
|
||||
if (empty($article_thumbnail))
|
||||
$article_thumbnail = $article_html->find('span.imageContainer', 0);
|
||||
}
|
||||
if (is_object($article_thumbnail)) {
|
||||
if (is_object($article_thumbnail))
|
||||
$article_thumbnail = $article_thumbnail->find('img', 0)->src;
|
||||
}
|
||||
|
||||
$article_content .= trim(
|
||||
$this->cleanArticle(
|
||||
extractFromDelimiters(
|
||||
$article_html,
|
||||
'<article',
|
||||
'<footer'
|
||||
$article_html, '<article', '<footer'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $article_uri;
|
||||
$item['title'] = $article_title;
|
||||
$item['author'] = $article_author;
|
||||
$item['timestamp'] = $article_timestamp;
|
||||
$item['enclosures'] = [$article_thumbnail];
|
||||
$item['enclosures'] = array($article_thumbnail);
|
||||
$item['content'] = $article_content;
|
||||
$this->items[] = $item;
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
<?php
|
||||
|
||||
class CNETFranceBridge extends FeedExpander
|
||||
{
|
||||
const MAINTAINER = 'leomaradan';
|
||||
@@ -7,25 +6,25 @@ class CNETFranceBridge extends FeedExpander
|
||||
const URI = 'https://www.cnetfrance.fr/';
|
||||
const CACHE_TIMEOUT = 3600; // 1h
|
||||
const DESCRIPTION = 'CNET France RSS with filters';
|
||||
const PARAMETERS = [
|
||||
'filters' => [
|
||||
'title' => [
|
||||
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' => [
|
||||
),
|
||||
'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()
|
||||
{
|
||||
|
@@ -7,30 +7,29 @@
|
||||
// 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
|
||||
{
|
||||
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 = [[
|
||||
const PARAMETERS = array(array(
|
||||
// The Vendor ID can be taken from the URL
|
||||
'vendor_id' => [
|
||||
'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' => [
|
||||
'product_id' => array(
|
||||
'name' => 'Product ID',
|
||||
'type' => 'number',
|
||||
'required' => false,
|
||||
'exampleValue' => 128, // PHP
|
||||
],
|
||||
]];
|
||||
),
|
||||
));
|
||||
|
||||
private $html = null;
|
||||
private $vendor = '';
|
||||
@@ -40,8 +39,7 @@ class CVEDetailsBridge extends BridgeAbstract
|
||||
// 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()
|
||||
{
|
||||
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');
|
||||
@@ -56,12 +54,11 @@ class CVEDetailsBridge extends BridgeAbstract
|
||||
|
||||
// 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());
|
||||
private function _fetchContent() {
|
||||
$html = getSimpleHTMLDOM($this->_buildURL());
|
||||
$this->html = defaultLinkTo($html, self::URI);
|
||||
|
||||
$vendor = $html->find('#contentdiv h1 > a', 0);
|
||||
$vendor = $html->find('#contentdiv > h1 > a', 0);
|
||||
if ($vendor == null) {
|
||||
returnServerError('Invalid Vendor ID ' .
|
||||
$this->getInput('vendor_id') .
|
||||
@@ -70,21 +67,20 @@ class CVEDetailsBridge extends BridgeAbstract
|
||||
}
|
||||
$this->vendor = $vendor->innertext;
|
||||
|
||||
$product = $html->find('#contentdiv h1 > a', 1);
|
||||
$product = $html->find('#contentdiv > h1 > a', 1);
|
||||
if ($product != null) {
|
||||
$this->product = $product->innertext;
|
||||
}
|
||||
}
|
||||
|
||||
// Build the name of the feed.
|
||||
public function getName()
|
||||
{
|
||||
public function getName() {
|
||||
if ($this->getInput('vendor_id') == '') {
|
||||
return self::NAME;
|
||||
}
|
||||
|
||||
if ($this->html == null) {
|
||||
$this->fetchContent();
|
||||
$this->_fetchContent();
|
||||
}
|
||||
|
||||
$name = 'CVE Vulnerabilities for ' . $this->vendor;
|
||||
@@ -96,50 +92,44 @@ class CVEDetailsBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
// Pull the data from the HTML response and fill the items..
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
if ($this->html == null) {
|
||||
$this->fetchContent();
|
||||
$this->_fetchContent();
|
||||
}
|
||||
|
||||
foreach ($this->html->find('#searchresults > .row') as $i => $tr) {
|
||||
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 = [];
|
||||
$categories = array($this->vendor);
|
||||
$enclosures = array();
|
||||
|
||||
$detailLink = $tr->find('.cveheader > h3 > a', 0);
|
||||
$detailHtml = getSimpleHTMLDOM($detailLink->href);
|
||||
|
||||
$div = $detailHtml->find('.cvedetailssummary', 0);
|
||||
|
||||
// The CVE number itself
|
||||
$title = $div->find('h1 > a', 0)->innertext;
|
||||
$content = $div->find('.ssc-paragraph', 0)->innertext;
|
||||
$cweList = $detailHtml->find('h2', 2)->next_sibling();
|
||||
foreach ($cweList->find('li') as $li) {
|
||||
$cweWithDescription = $li->find('a', 0)->innertext;
|
||||
preg_match('/CWE-(\d+)/', $cweWithDescription, $cwe);
|
||||
if (count($cwe) > 1) {
|
||||
$categories[] = 'CWE-' . $cwe[1];
|
||||
$enclosures[] = 'https://cwe.mitre.org/data/definitions/' . $cwe[1] . '.html';
|
||||
$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;
|
||||
}
|
||||
|
||||
$this->items[] = [
|
||||
'uri' => 'https://cvedetails.com/' . $detailHtml->find('h1 > a', 0)->href,
|
||||
// The CVE number itself
|
||||
$title = $tr->find('td', 1)->find('a', 0)->innertext;
|
||||
|
||||
$this->items[] = array(
|
||||
'uri' => $tr->find('td', 1)->find('a', 0)->href,
|
||||
'title' => $title,
|
||||
'timestamp' => $tr->find('td', 5)->innertext,
|
||||
'content' => $content,
|
||||
'content' => $tr->next_sibling()->innertext,
|
||||
'categories' => $categories,
|
||||
'enclosures' => $enclosures,
|
||||
'uid' => $title,
|
||||
];
|
||||
'uid' => $tr->find('td', 1)->find('a', 0)->innertext,
|
||||
);
|
||||
|
||||
// We only want to fetch the latest 10 CVEs
|
||||
if (count($this->items) >= 10) {
|
||||
|
@@ -1,32 +1,30 @@
|
||||
<?php
|
||||
|
||||
class CachetBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
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' => [
|
||||
), '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()
|
||||
{
|
||||
public function getURI() {
|
||||
return $this->getInput('host') === null ? 'https://cachethq.io/' : $this->getInput('host');
|
||||
}
|
||||
|
||||
@@ -36,8 +34,7 @@ class CachetBridge extends BridgeAbstract
|
||||
* @param string $ping
|
||||
* @return boolean
|
||||
*/
|
||||
private function validatePing($ping)
|
||||
{
|
||||
private function validatePing($ping) {
|
||||
$ping = json_decode($ping);
|
||||
if ($ping === null) {
|
||||
return false;
|
||||
@@ -51,8 +48,7 @@ class CachetBridge extends BridgeAbstract
|
||||
* @param integer $id
|
||||
* @return string
|
||||
*/
|
||||
private function getComponentName($id)
|
||||
{
|
||||
private function getComponentName($id) {
|
||||
if ($id === 0) {
|
||||
return '';
|
||||
}
|
||||
@@ -68,8 +64,7 @@ class CachetBridge extends BridgeAbstract
|
||||
return $component->data->name;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$ping = getContents(urljoin($this->getURI(), '/api/v1/ping'));
|
||||
if (!$this->validatePing($ping)) {
|
||||
returnClientError('Provided URI is invalid!');
|
||||
@@ -89,6 +84,7 @@ class CachetBridge extends BridgeAbstract
|
||||
});
|
||||
|
||||
foreach ($incidents->data as $incident) {
|
||||
|
||||
if (isset($incident->permalink)) {
|
||||
$permalink = $incident->permalink;
|
||||
} else {
|
||||
@@ -118,13 +114,13 @@ class CachetBridge extends BridgeAbstract
|
||||
$uidOrig = $permalink . $incident->created_at;
|
||||
$uid = hash('sha512', $uidOrig);
|
||||
$timestamp = strtotime($incident->created_at);
|
||||
$categories = [];
|
||||
$categories = array();
|
||||
$categories[] = $incident->human_status;
|
||||
if ($componentName !== '') {
|
||||
$categories[] = $componentName;
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $permalink;
|
||||
$item['title'] = $title;
|
||||
$item['timestamp'] = $timestamp;
|
||||
|
@@ -1,45 +1,41 @@
|
||||
<?php
|
||||
|
||||
class CarThrottleBridge extends BridgeAbstract
|
||||
{
|
||||
class CarThrottleBridge extends FeedExpander {
|
||||
const NAME = 'Car Throttle ';
|
||||
const URI = 'https://www.carthrottle.com/';
|
||||
const URI = 'https://www.carthrottle.com';
|
||||
const DESCRIPTION = 'Get the latest car-related news from Car Throttle.';
|
||||
const MAINTAINER = 't0stiman';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$news = getSimpleHTMLDOMCached(self::URI . 'news')
|
||||
or returnServerError('could not retrieve page');
|
||||
|
||||
$this->items[] = [];
|
||||
|
||||
//for each post
|
||||
foreach ($news->find('div.cmg-card') as $post) {
|
||||
$item = [];
|
||||
|
||||
$titleElement = $post->find('div.title a.cmg-link')[0];
|
||||
$item['uri'] = self::URI . $titleElement->getAttribute('href');
|
||||
$item['title'] = $titleElement->innertext;
|
||||
|
||||
$articlePage = getSimpleHTMLDOMCached($item['uri'])
|
||||
or returnServerError('could not retrieve page');
|
||||
|
||||
$item['author'] = $articlePage->find('div.author div')[1]->innertext;
|
||||
|
||||
$dinges = $articlePage->find('div.main-body')[0];
|
||||
//remove ads
|
||||
foreach ($dinges->find('aside') as $ad) {
|
||||
$ad->outertext = '';
|
||||
$dinges->save();
|
||||
public function collectData() {
|
||||
$this->collectExpandableDatas('https://www.carthrottle.com/rss', 10);
|
||||
}
|
||||
|
||||
$item['content'] = $articlePage->find('div.summary')[0] .
|
||||
$articlePage->find('figure.main-image')[0] .
|
||||
$dinges;
|
||||
protected function parseItem($feedItem) {
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
//add the item to the list
|
||||
array_push($this->items, $item);
|
||||
//fetch page
|
||||
$articlePage = getSimpleHTMLDOMCached($feedItem->link)
|
||||
or returnServerError('Could not retrieve ' . $feedItem->link);
|
||||
|
||||
$subtitle = $articlePage->find('p.standfirst', 0);
|
||||
$article = $articlePage->find('div.content_field', 0);
|
||||
|
||||
$item['content'] = str_get_html($subtitle . $article);
|
||||
|
||||
//convert <iframe>s to <a>s. meant for embedded videos.
|
||||
foreach($item['content']->find('iframe') as $found) {
|
||||
|
||||
$iframeUrl = $found->getAttribute('src');
|
||||
|
||||
if ($iframeUrl) {
|
||||
$found->outertext = '<a href="' . $iframeUrl . '">' . $iframeUrl . '</a>';
|
||||
}
|
||||
}
|
||||
|
||||
//remove scripts from the text
|
||||
foreach ($item['content']->find('script') as $remove) {
|
||||
$remove->outertext = '';
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
@@ -1,69 +1,61 @@
|
||||
<?php
|
||||
|
||||
class CastorusBridge extends BridgeAbstract
|
||||
{
|
||||
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 = [
|
||||
'Get latest changes' => [],
|
||||
'Get latest changes via ZIP code' => [
|
||||
'zip' => [
|
||||
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' => [
|
||||
'city' => [
|
||||
)
|
||||
),
|
||||
'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'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
// Extracts the title from an actitiy
|
||||
private function extractActivityTitle($activity)
|
||||
{
|
||||
private function extractActivityTitle($activity){
|
||||
$title = $activity->find('a', 0);
|
||||
|
||||
if (!$title) {
|
||||
if(!$title)
|
||||
returnServerError('Cannot find title!');
|
||||
}
|
||||
|
||||
return trim($title->plaintext);
|
||||
return htmlspecialchars(trim($title->plaintext));
|
||||
}
|
||||
|
||||
// Extracts the url from an actitiy
|
||||
private function extractActivityUrl($activity)
|
||||
{
|
||||
private function extractActivityUrl($activity){
|
||||
$url = $activity->find('a', 0);
|
||||
|
||||
if (!$url) {
|
||||
if(!$url)
|
||||
returnServerError('Cannot find url!');
|
||||
}
|
||||
|
||||
return self::URI . $url->href;
|
||||
}
|
||||
|
||||
// Extracts the time from an activity
|
||||
private function extractActivityTime($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) {
|
||||
if(!$nodes)
|
||||
returnServerError('Cannot find nodes!');
|
||||
}
|
||||
|
||||
foreach($nodes as $node) {
|
||||
$node->outertext = '';
|
||||
@@ -73,36 +65,31 @@ class CastorusBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
// Extracts the price change
|
||||
private function extractActivityPrice($activity)
|
||||
{
|
||||
private function extractActivityPrice($activity){
|
||||
$price = $activity->find('span', 1);
|
||||
|
||||
if (!$price) {
|
||||
if(!$price)
|
||||
returnServerError('Cannot find price!');
|
||||
}
|
||||
|
||||
return $price->innertext;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$zip_filter = trim($this->getInput('zip'));
|
||||
$city_filter = trim($this->getInput('city'));
|
||||
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
if (!$html) {
|
||||
if(!$html)
|
||||
returnServerError('Could not load data from ' . self::URI . '!');
|
||||
}
|
||||
|
||||
$activities = $html->find('div#activite > li');
|
||||
|
||||
if (!$activities) {
|
||||
if(!$activities)
|
||||
returnServerError('Failed to find activities!');
|
||||
}
|
||||
|
||||
foreach($activities as $activity) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $this->extractActivityTitle($activity);
|
||||
$item['uri'] = $this->extractActivityUrl($activity);
|
||||
@@ -115,17 +102,13 @@ class CastorusBridge extends BridgeAbstract
|
||||
. $this->extractActivityPrice($activity)
|
||||
. '</p>';
|
||||
|
||||
if (
|
||||
isset($zip_filter)
|
||||
&& !(substr($item['title'], 0, strlen($zip_filter)) === $zip_filter)
|
||||
) {
|
||||
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)
|
||||
) {
|
||||
if(isset($city_filter)
|
||||
&& !(substr($item['title'], strpos($item['title'], ' ') + 1, strlen($city_filter)) === $city_filter)) {
|
||||
continue; // Skip this item
|
||||
}
|
||||
|
||||
|
@@ -1,42 +1,40 @@
|
||||
<?php
|
||||
|
||||
class CdactionBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array( array(
|
||||
'category' => array(
|
||||
'name' => 'Kategoria',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Najnowsze (wszystkie)' => 'najnowsze',
|
||||
'Newsy' => 'newsy',
|
||||
'Recenzje' => 'recenzje',
|
||||
'Teksty' => [
|
||||
'Teksty' => array(
|
||||
'Publicystyka' => 'publicystyka',
|
||||
'Zapowiedzi' => 'zapowiedzi',
|
||||
'Już graliśmy' => 'juz-gralismy',
|
||||
'Poradniki' => 'poradniki',
|
||||
],
|
||||
),
|
||||
'Kultura' => 'kultura',
|
||||
'Wideo' => 'wideo',
|
||||
'Czasopismo' => 'czasopismo',
|
||||
'Technologie' => [
|
||||
'Technologie' => array(
|
||||
'Artykuły' => 'artykuly',
|
||||
'Testy' => 'testy',
|
||||
],
|
||||
'Na luzie' => [
|
||||
),
|
||||
'Na luzie' => array(
|
||||
'Konkursy' => 'konkursy',
|
||||
'Nadgodziny' => 'nadgodziny',
|
||||
]
|
||||
]
|
||||
]]
|
||||
];
|
||||
)
|
||||
)
|
||||
))
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/' . $this->getInput('category'));
|
||||
|
||||
$newsJson = $html->find('script#__NEXT_DATA__', 0)->innertext;
|
||||
@@ -46,7 +44,7 @@ class CdactionBridge extends BridgeAbstract
|
||||
|
||||
$queriesIndex = $this->getInput('category') === 'najnowsze' ? 0 : 1;
|
||||
foreach ($newsJson->props->pageProps->dehydratedState->queries[$queriesIndex]->state->data->results as $news) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $this->getURI() . '/' . $news->category->slug . '/' . $news->slug;
|
||||
$item['title'] = $news->title;
|
||||
$item['timestamp'] = $news->publishedAt;
|
||||
|
@@ -1,30 +1,28 @@
|
||||
<?php
|
||||
|
||||
class CeskaTelevizeBridge extends BridgeAbstract
|
||||
{
|
||||
class CeskaTelevizeBridge extends BridgeAbstract {
|
||||
|
||||
const NAME = 'Česká televize Bridge';
|
||||
const URI = 'https://www.ceskatelevize.cz';
|
||||
const CACHE_TIMEOUT = 3600;
|
||||
const DESCRIPTION = 'Return newest videos';
|
||||
const MAINTAINER = 'kolarcz';
|
||||
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'url' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'url' => array(
|
||||
'name' => 'url to the show',
|
||||
'required' => true,
|
||||
'exampleValue' => 'https://www.ceskatelevize.cz/porady/1097181328-udalosti/'
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
private function fixChars($text)
|
||||
{
|
||||
private function fixChars($text) {
|
||||
return html_entity_decode($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
private function getUploadTimeFromString($string)
|
||||
{
|
||||
private function getUploadTimeFromString($string) {
|
||||
if (strpos($string, 'dnes') !== false) {
|
||||
return strtotime('today');
|
||||
} elseif (strpos($string, 'včera') !== false) {
|
||||
@@ -33,12 +31,11 @@ class CeskaTelevizeBridge extends BridgeAbstract
|
||||
returnServerError('Could not get date from Česká televize string');
|
||||
}
|
||||
|
||||
$date = sprintf('%04d-%02d-%02d', $match[3] ?? date('Y'), $match[2], $match[1]);
|
||||
$date = sprintf('%04d-%02d-%02d', isset($match[3]) ? $match[3] : date('Y'), $match[2], $match[1]);
|
||||
return strtotime($date);
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$url = $this->getInput('url');
|
||||
|
||||
$validUrl = '/^(https:\/\/www\.ceskatelevize\.cz\/porady\/\d+-[a-z0-9-]+\/)(bonus\/)?$/';
|
||||
@@ -46,7 +43,7 @@ class CeskaTelevizeBridge extends BridgeAbstract
|
||||
returnServerError('Invalid url');
|
||||
}
|
||||
|
||||
$category = $match[4] ?? 'nove';
|
||||
$category = isset($match[4]) ? $match[4] : 'nove';
|
||||
$fixedUrl = "{$match[1]}dily/{$category}/";
|
||||
|
||||
$html = getSimpleHTMLDOM($fixedUrl);
|
||||
@@ -64,25 +61,23 @@ class CeskaTelevizeBridge extends BridgeAbstract
|
||||
$itemThumbnail = $element->find('img', 0);
|
||||
$itemUri = self::URI . $element->getAttribute('href');
|
||||
|
||||
$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)
|
||||
];
|
||||
);
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
return $this->feedUri ?? parent::getURI();
|
||||
public function getURI() {
|
||||
return isset($this->feedUri) ? $this->feedUri : parent::getURI();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return $this->feedName ?? parent::getName();
|
||||
public function getName() {
|
||||
return isset($this->feedName) ? $this->feedName : parent::getName();
|
||||
}
|
||||
}
|
||||
|
@@ -1,72 +1,69 @@
|
||||
<?php
|
||||
|
||||
class CodebergBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
'Commits' => array(
|
||||
'branch' => array(
|
||||
'name' => 'branch',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'main',
|
||||
'required' => false,
|
||||
'title' => 'Optional, main branch is used by default.',
|
||||
],
|
||||
],
|
||||
'Issues' => [],
|
||||
'Issue Comments' => [
|
||||
'issueId' => [
|
||||
),
|
||||
),
|
||||
'Issues' => array(),
|
||||
'Issue Comments' => array(
|
||||
'issueId' => array(
|
||||
'name' => 'Issue ID',
|
||||
'type' => 'text',
|
||||
'required' => true,
|
||||
'exampleValue' => '513',
|
||||
]
|
||||
],
|
||||
'Pull Requests' => [],
|
||||
'Releases' => [],
|
||||
'Tags' => [],
|
||||
'global' => [
|
||||
'username' => [
|
||||
)
|
||||
),
|
||||
'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' => [
|
||||
),
|
||||
'repo' => array(
|
||||
'name' => 'Repository',
|
||||
'type' => 'text',
|
||||
'exampleValue' => 'Community',
|
||||
'required' => true,
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
const CACHE_TIMEOUT = 1800;
|
||||
|
||||
const TEST_DETECT_PARAMETERS = [
|
||||
'https://codeberg.org/Codeberg/Community/issues/507' => [
|
||||
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' => [
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/issues' => array(
|
||||
'context' => 'Issues', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
],
|
||||
'https://codeberg.org/Codeberg/Community/pulls' => [
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/pulls' => array(
|
||||
'context' => 'Pull Requests', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
],
|
||||
'https://codeberg.org/Codeberg/Community/releases' => [
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/releases' => array(
|
||||
'context' => 'Releases', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
],
|
||||
'https://codeberg.org/Codeberg/Community/commits/branch/master' => [
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/commits/branch/master' => array(
|
||||
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community', 'branch' => 'master'
|
||||
],
|
||||
'https://codeberg.org/Codeberg/Community/commits' => [
|
||||
),
|
||||
'https://codeberg.org/Codeberg/Community/commits' => array(
|
||||
'context' => 'Commits', 'username' => 'Codeberg', 'repo' => 'Community'
|
||||
]
|
||||
];
|
||||
)
|
||||
);
|
||||
|
||||
private $defaultBranch = 'main';
|
||||
private $issueTitle = '';
|
||||
@@ -77,338 +74,8 @@ class CodebergBridge extends BridgeAbstract
|
||||
private $releasesUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/releases/';
|
||||
private $issueCommentsUrlRegex = '/codeberg\.org\/([\w]+)\/([\w]+)\/issues\/([0-9]+)/';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
switch ($this->queriedContext) {
|
||||
case 'Commits':
|
||||
$this->extractCommits($html);
|
||||
break;
|
||||
case 'Issues':
|
||||
$this->extractIssues($html);
|
||||
break;
|
||||
case 'Issue Comments':
|
||||
$this->extractIssueComments($html);
|
||||
break;
|
||||
case 'Pull Requests':
|
||||
$this->extractPulls($html);
|
||||
break;
|
||||
case 'Releases':
|
||||
$this->extractReleases($html);
|
||||
break;
|
||||
case 'Tags':
|
||||
$this->extractTags($html);
|
||||
break;
|
||||
default:
|
||||
throw new \Exception('Invalid context: ' . $this->queriedContext);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
switch ($this->queriedContext) {
|
||||
case 'Commits':
|
||||
if ($this->getBranch() === $this->defaultBranch) {
|
||||
return $this->getRepo() . ' Commits';
|
||||
}
|
||||
|
||||
return $this->getRepo() . ' Commits (' . $this->getBranch() . ' branch) - ' . self::NAME;
|
||||
case 'Issues':
|
||||
return $this->getRepo() . ' Issues - ' . self::NAME;
|
||||
case 'Issue Comments':
|
||||
return $this->issueTitle . ' - Issue Comments - ' . self::NAME;
|
||||
case 'Pull Requests':
|
||||
return $this->getRepo() . ' Pull Requests - ' . self::NAME;
|
||||
case 'Releases':
|
||||
return $this->getRepo() . ' Releases - ' . self::NAME;
|
||||
case 'Tags':
|
||||
return $this->getRepo() . ' Tags - ' . self::NAME;
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
switch ($this->queriedContext) {
|
||||
case 'Commits':
|
||||
return self::URI . $this->getRepo() . '/commits/branch/' . $this->getBranch();
|
||||
case 'Issues':
|
||||
return self::URI . $this->getRepo() . '/issues/';
|
||||
case 'Issue Comments':
|
||||
return self::URI . $this->getRepo() . '/issues/' . $this->getInput('issueId');
|
||||
case 'Pull Requests':
|
||||
return self::URI . $this->getRepo() . '/pulls';
|
||||
case 'Releases':
|
||||
return self::URI . $this->getRepo() . '/releases';
|
||||
case 'Tags':
|
||||
return self::URI . $this->getRepo() . '/tags';
|
||||
default:
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
private function getBranch()
|
||||
{
|
||||
if ($this->getInput('branch')) {
|
||||
return $this->getInput('branch');
|
||||
}
|
||||
|
||||
return $this->defaultBranch;
|
||||
}
|
||||
|
||||
private function getRepo()
|
||||
{
|
||||
return $this->getInput('username') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract commits
|
||||
*/
|
||||
private function extractCommits($html)
|
||||
{
|
||||
$table = $html->find('table#commits-table', 0);
|
||||
$tbody = $table->find('tbody.commit-list', 0);
|
||||
|
||||
foreach ($tbody->find('tr') as $tr) {
|
||||
$item = [];
|
||||
|
||||
$message = $tr->find('td.message', 0);
|
||||
|
||||
$item['title'] = $message->find('span.message-wrapper', 0)->plaintext;
|
||||
$item['uri'] = $tr->find('td.sha', 0)->find('a', 0)->href;
|
||||
$item['author'] = $tr->find('td.author', 0)->plaintext;
|
||||
|
||||
$var = $tr->find('td', 3);
|
||||
$var1 = $var->find('span', 0);
|
||||
if ($var1) {
|
||||
$item['timestamp'] = $var1->title;
|
||||
}
|
||||
|
||||
if ($message->find('pre.commit-body', 0)) {
|
||||
$message->find('pre.commit-body', 0)->style = '';
|
||||
|
||||
$item['content'] = $message->find('pre.commit-body', 0);
|
||||
} else {
|
||||
$item['content'] = '<blockquote>' . $item['title'] . '</blockquote>';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract issues
|
||||
*/
|
||||
private function extractIssues($html)
|
||||
{
|
||||
$div = $html->find('div.issue.list', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = [];
|
||||
|
||||
$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;
|
||||
|
||||
$time = $li->find('relative-time.time-since', 0);
|
||||
if ($time) {
|
||||
$item['timestamp'] = $time->datetime;
|
||||
}
|
||||
|
||||
$item['author'] = $li->find('div.desc', 0)->find('a', 1)->plaintext;
|
||||
|
||||
// Fetch issue page
|
||||
$issuePage = getSimpleHTMLDOMCached($item['uri'], 3600);
|
||||
$issuePage = defaultLinkTo($issuePage, self::URI);
|
||||
|
||||
$item['content'] = $issuePage->find('div.timeline-item.comment.first', 0)->find('div.render-content.markup', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract issue comments
|
||||
*/
|
||||
private function extractIssueComments($html)
|
||||
{
|
||||
$this->issueTitle = $html->find('span#issue-title', 0)->plaintext
|
||||
. ' (' . $html->find('span.index', 0)->plaintext . ')';
|
||||
|
||||
foreach ($html->find('div.timeline-item.comment') as $div) {
|
||||
$item = [];
|
||||
|
||||
if ($div->class === 'timeline-item comment merge box') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
if ($div->find('div.dropzone-attachments', 0)) {
|
||||
$item['content'] .= $div->find('div.dropzone-attachments', 0);
|
||||
}
|
||||
|
||||
$item['author'] = $div->find('a.author', 0)->innertext;
|
||||
$item['timestamp'] = $div->find('span.time-since', 0)->title;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract pulls
|
||||
*/
|
||||
private function extractPulls($html)
|
||||
{
|
||||
$div = $html->find('div.issue.list', 0);
|
||||
|
||||
foreach ($div->find('li.item') as $li) {
|
||||
$item = [];
|
||||
|
||||
$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;
|
||||
|
||||
$time = $li->find('relative-time.time-since', 0);
|
||||
if ($time) {
|
||||
$item['timestamp'] = $time->datetime;
|
||||
}
|
||||
|
||||
$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);
|
||||
|
||||
$var = $pullRequestPage->find('ui.timeline', 0);
|
||||
if ($var) {
|
||||
$var1 = $var->find('div.render-content.markup', 0);
|
||||
$item['content'] = $var1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
$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;
|
||||
|
||||
$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>
|
||||
<p>{$commit}</p>
|
||||
{$downloads}
|
||||
HTML;
|
||||
|
||||
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('span.author', 0)->find('a', 0)->plaintext;
|
||||
|
||||
$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 = '';
|
||||
|
||||
foreach ($html->find('a') as $index => $a) {
|
||||
if ($skipFirst === true && $index === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$downloads .= <<<HTML
|
||||
<a href="{$a->herf}">{$a->plaintext}</a><br>
|
||||
HTML;
|
||||
}
|
||||
|
||||
return <<<EOD
|
||||
<strong>Downloads</strong>
|
||||
<p>{$downloads}</p>
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip SVG tag
|
||||
*/
|
||||
private function stripSvg($html)
|
||||
{
|
||||
if ($html->find('svg', 0)) {
|
||||
$html->find('svg', 0)->outertext = '';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
$params = [];
|
||||
public function detectParameters($url) {
|
||||
$params = array();
|
||||
|
||||
// Issue Comments
|
||||
if(preg_match($this->issueCommentsUrlRegex, $url, $matches)) {
|
||||
@@ -462,4 +129,275 @@ EOD;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function collectData() {
|
||||
$html = getSimpleHTMLDOM($this->getURI());
|
||||
|
||||
$html = defaultLinkTo($html, $this->getURI());
|
||||
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
$this->extractCommits($html);
|
||||
break;
|
||||
case 'Issues':
|
||||
$this->extractIssues($html);
|
||||
break;
|
||||
case 'Issue Comments':
|
||||
$this->extractIssueComments($html);
|
||||
break;
|
||||
case 'Pull Requests':
|
||||
$this->extractPulls($html);
|
||||
break;
|
||||
case 'Releases':
|
||||
$this->extractReleases($html);
|
||||
break;
|
||||
default:
|
||||
returnClientError('Invalid context: ' . $this->queriedContext);
|
||||
}
|
||||
}
|
||||
|
||||
public function getName() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
if ($this->getBranch() === $this->defaultBranch) {
|
||||
return $this->getRepo() . ' Commits';
|
||||
}
|
||||
|
||||
return $this->getRepo() . ' Commits (' . $this->getBranch() . ' branch) - ' . self::NAME;
|
||||
case 'Issues':
|
||||
return $this->getRepo() . ' Issues - ' . self::NAME;
|
||||
case 'Issue Comments':
|
||||
return $this->issueTitle . ' - Issue Comments - ' . self::NAME;
|
||||
case 'Pull Requests':
|
||||
return $this->getRepo() . ' Pull Requests - ' . self::NAME;
|
||||
case 'Releases':
|
||||
return $this->getRepo() . ' Releases - ' . self::NAME;
|
||||
default:
|
||||
return parent::getName();
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI() {
|
||||
switch($this->queriedContext) {
|
||||
case 'Commits':
|
||||
return self::URI . $this->getRepo() . '/commits/branch/' . $this->getBranch();
|
||||
case 'Issues':
|
||||
return self::URI . $this->getRepo() . '/issues/';
|
||||
case 'Issue Comments':
|
||||
return self::URI . $this->getRepo() . '/issues/' . $this->getInput('issueId');
|
||||
case 'Pull Requests':
|
||||
return self::URI . $this->getRepo() . '/pulls';
|
||||
case 'Releases':
|
||||
return self::URI . $this->getRepo() . '/releases';
|
||||
default:
|
||||
return parent::getURI();
|
||||
}
|
||||
}
|
||||
|
||||
private function getBranch() {
|
||||
if ($this->getInput('branch')) {
|
||||
return $this->getInput('branch');
|
||||
}
|
||||
|
||||
return $this->defaultBranch;
|
||||
}
|
||||
|
||||
private function getRepo() {
|
||||
return $this->getInput('username') . '/' . $this->getInput('repo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract commits
|
||||
*/
|
||||
private function extractCommits($html) {
|
||||
$table = $html->find('table#commits-table', 0);
|
||||
$tbody = $table->find('tbody.commit-list', 0);
|
||||
|
||||
foreach ($tbody->find('tr') as $tr) {
|
||||
$item = array();
|
||||
|
||||
$message = $tr->find('td.message', 0);
|
||||
|
||||
$item['title'] = $message->find('span.message-wrapper', 0)->plaintext;
|
||||
$item['uri'] = $tr->find('td.sha', 0)->find('a', 0)->href;
|
||||
$item['author'] = $tr->find('td.author', 0)->plaintext;
|
||||
$item['timestamp'] = $tr->find('td', 3)->find('span', 0)->title;
|
||||
|
||||
if ($message->find('pre.commit-body', 0)) {
|
||||
$message->find('pre.commit-body', 0)->style = '';
|
||||
|
||||
$item['content'] = $message->find('pre.commit-body', 0);
|
||||
} else {
|
||||
$item['content'] = '<blockquote>' . $item['title'] . '</blockquote>';
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract issues
|
||||
*/
|
||||
private function extractIssues($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 issue page
|
||||
$issuePage = getSimpleHTMLDOMCached($item['uri'], 3600);
|
||||
$issuePage = defaultLinkTo($issuePage, self::URI);
|
||||
|
||||
$item['content'] = $issuePage->find('div.timeline-item.comment.first', 0)->find('div.render-content.markup', 0);
|
||||
|
||||
foreach ($li->find('a.ui.label') as $label) {
|
||||
$item['categories'][] = $label->plaintext;
|
||||
}
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract issue comments
|
||||
*/
|
||||
private function extractIssueComments($html) {
|
||||
$this->issueTitle = $html->find('span#issue-title', 0)->plaintext
|
||||
. ' (' . $html->find('span.index', 0)->plaintext . ')';
|
||||
|
||||
foreach ($html->find('div.timeline-item.comment') as $div) {
|
||||
$item = array();
|
||||
|
||||
if ($div->class === 'timeline-item comment merge box') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$item['title'] = $this->ellipsisTitle($div->find('div.render-content.markup', 0)->plaintext);
|
||||
$item['uri'] = $div->find('span.text.grey', 0)->find('a', 1)->href;
|
||||
$item['content'] = $div->find('div.render-content.markup', 0);
|
||||
|
||||
if ($div->find('div.dropzone-attachments', 0)) {
|
||||
$item['content'] .= $div->find('div.dropzone-attachments', 0);
|
||||
}
|
||||
|
||||
$item['author'] = $div->find('a.author', 0)->innertext;
|
||||
$item['timestamp'] = $div->find('span.time-since', 0)->title;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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>
|
||||
<p>{$commit}</p>
|
||||
{$downloads}
|
||||
HTML;
|
||||
|
||||
$item['timestamp'] = $li->find('span.time', 0)->find('span', 0)->title;
|
||||
$item['author'] = $li->find('span.author', 0)->find('a', 0)->plaintext;
|
||||
|
||||
$this->items[] = $item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
$downloads .= <<<HTML
|
||||
<a href="{$a->herf}">{$a->plaintext}</a><br>
|
||||
HTML;
|
||||
}
|
||||
|
||||
return <<<EOD
|
||||
<strong>Downloads</strong>
|
||||
<p>{$downloads}</p>
|
||||
EOD;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip SVG tag
|
||||
*/
|
||||
private function stripSvg($html) {
|
||||
if ($html->find('svg', 0)) {
|
||||
$html->find('svg', 0)->outertext = '';
|
||||
}
|
||||
|
||||
return $html;
|
||||
}
|
||||
}
|
||||
|
@@ -1,16 +1,14 @@
|
||||
<?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';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$months = [
|
||||
public function collectData(){
|
||||
$months = array(
|
||||
'01' => 'janv.',
|
||||
'02' => 'févr.',
|
||||
'03' => 'mars',
|
||||
@@ -23,7 +21,7 @@ class CollegeDeFranceBridge extends BridgeAbstract
|
||||
'10' => 'oct.',
|
||||
'11' => 'nov.',
|
||||
'12' => 'déc.'
|
||||
];
|
||||
);
|
||||
|
||||
// The "API" used by the site returns a list of partial HTML in this form
|
||||
/* <li>
|
||||
@@ -39,7 +37,7 @@ class CollegeDeFranceBridge extends BridgeAbstract
|
||||
. 'components/search-audiovideo.jsp?fulltext=&siteid=1156951719600&lang=FR&type=all');
|
||||
|
||||
foreach($html->find('a[data-target]') as $element) {
|
||||
$item = [];
|
||||
$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
|
||||
|
@@ -1,23 +1,20 @@
|
||||
<?php
|
||||
|
||||
class ComboiosDePortugalBridge extends BridgeAbstract
|
||||
{
|
||||
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()
|
||||
{
|
||||
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', [], [
|
||||
$html = getSimpleHTMLDOM($this->getURI() . '/consultar-horarios/avisos', array(), array(
|
||||
CURLOPT_SSL_VERIFYPEER => 0,
|
||||
]);
|
||||
));
|
||||
|
||||
foreach($html->find('.warnings-table a') as $element) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
$item['title'] = $element->innertext;
|
||||
$item['uri'] = self::BASE_URI . implode('/', array_map('urlencode', explode('/', $element->href)));
|
||||
|
@@ -1,34 +1,31 @@
|
||||
<?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' => [
|
||||
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);
|
||||
;
|
||||
$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 = [];
|
||||
$item = array();
|
||||
|
||||
$page = getSimpleHTMLDOM($link);
|
||||
|
||||
@@ -45,14 +42,11 @@ class ComicsKingdomBridge extends BridgeAbstract
|
||||
|
||||
$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
|
||||
}
|
||||
if (empty($link)) break; // allow bridge to continue if there's less than 3 comics
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI(){
|
||||
if(!is_null($this->getInput('comicname'))) {
|
||||
return self::URI . urlencode($this->getInput('comicname'));
|
||||
}
|
||||
@@ -60,8 +54,7 @@ class ComicsKingdomBridge extends BridgeAbstract
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
public function getName(){
|
||||
if(!is_null($this->getInput('comicname'))) {
|
||||
return $this->getInput('comicname') . ' - Comics Kingdom';
|
||||
}
|
||||
|
@@ -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.';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas('http://www.commondreams.org/rss.xml', 10);
|
||||
}
|
||||
|
||||
protected function parseItem($newsItem)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -1,15 +1,13 @@
|
||||
<?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';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$table = $html->find('table table', 2);
|
||||
@@ -18,7 +16,7 @@ class CopieDoubleBridge extends BridgeAbstract
|
||||
$td = $element->find('td', 0);
|
||||
|
||||
if($td->class === 'couleur_1') {
|
||||
$item = [];
|
||||
$item = array();
|
||||
$title = $td->innertext;
|
||||
$pos = strpos($title, '<a');
|
||||
$title = substr($title, 0, $pos);
|
||||
|
@@ -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,
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,20 +1,17 @@
|
||||
<?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';
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$this->collectExpandableDatas(static::URI . 'feed/all/rss.xml', 20);
|
||||
}
|
||||
|
||||
protected function parseItem($feedItem)
|
||||
{
|
||||
protected function parseItem($feedItem){
|
||||
$item = parent::parseItem($feedItem);
|
||||
|
||||
$articlePage = getSimpleHTMLDOMCached($feedItem->link);
|
||||
|
@@ -1,55 +1,52 @@
|
||||
<?php
|
||||
|
||||
class CraigslistBridge extends BridgeAbstract
|
||||
{
|
||||
class CraigslistBridge extends BridgeAbstract {
|
||||
const MAINTAINER = 'Yaman Qalieh';
|
||||
const NAME = 'Craigslist Bridge';
|
||||
const URI = 'https://craigslist.org/';
|
||||
const DESCRIPTION = 'Returns craigslist search results';
|
||||
|
||||
const PARAMETERS = [ [
|
||||
'region' => [
|
||||
const PARAMETERS = array( array(
|
||||
'region' => array(
|
||||
'name' => 'Region',
|
||||
'title' => 'The subdomain before craigslist.org in the URL',
|
||||
'exampleValue' => 'sfbay',
|
||||
'required' => true
|
||||
],
|
||||
'search' => [
|
||||
),
|
||||
'search' => array(
|
||||
'name' => 'Search Query',
|
||||
'title' => 'Everything in the URL after /search/',
|
||||
'exampleValue' => 'sya?query=laptop',
|
||||
'required' => true
|
||||
],
|
||||
'limit' => [
|
||||
),
|
||||
'limit' => array(
|
||||
'name' => 'Number of Posts',
|
||||
'type' => 'number',
|
||||
'title' => 'The maximum number of posts is 120. Use 0 for unlimited posts.',
|
||||
'defaultValue' => '25'
|
||||
]
|
||||
]];
|
||||
)
|
||||
));
|
||||
|
||||
const TEST_DETECT_PARAMETERS = [
|
||||
'https://sfbay.craigslist.org/search/sya?query=laptop' => [
|
||||
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' => [
|
||||
),
|
||||
'https://newyork.craigslist.org/search/sss?query=32gb+flash+drive&bundleDuplicates=1&max_price=20' => array(
|
||||
'region' => 'newyork', 'search' => 'sss?query=32gb+flash+drive&bundleDuplicates=1&max_price=20'
|
||||
],
|
||||
];
|
||||
),
|
||||
);
|
||||
|
||||
const URL_REGEX = '/^https:\/\/(?<region>\w+).craigslist.org\/search\/(?<search>.+)/';
|
||||
|
||||
public function detectParameters($url)
|
||||
{
|
||||
public function detectParameters($url) {
|
||||
if(preg_match(self::URL_REGEX, $url, $matches)) {
|
||||
$params = [];
|
||||
$params = array();
|
||||
$params['region'] = $matches['region'];
|
||||
$params['search'] = $matches['search'];
|
||||
return $params;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI() {
|
||||
if (!is_null($this->getInput('region'))) {
|
||||
$domain = 'https://' . $this->getInput('region') . '.craigslist.org/search/';
|
||||
return urljoin($domain, $this->getInput('search'));
|
||||
@@ -57,13 +54,12 @@ class CraigslistBridge extends BridgeAbstract
|
||||
return parent::getURI();
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
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') {
|
||||
if ($html->find('.displaycountShow', 0)->plaintext == '0') {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,24 +72,23 @@ class CraigslistBridge extends BridgeAbstract
|
||||
}
|
||||
|
||||
foreach($results as $post) {
|
||||
|
||||
// Skip "nearby results" banner and results
|
||||
// This only appears when searchNearby is not specified
|
||||
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;
|
||||
|
||||
$price = $post->find('.result-price', 0)->plaintext ?? '';
|
||||
$item['content'] = $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);
|
||||
. $post->find('.result-hood, span.nearby', 0)->plaintext;
|
||||
|
||||
$images = $post->find('.result-image[data-ids]', 0);
|
||||
if (!is_null($images)) {
|
||||
|
@@ -1,41 +1,39 @@
|
||||
<?php
|
||||
|
||||
class CrewbayBridge extends BridgeAbstract
|
||||
{
|
||||
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' => [
|
||||
const PARAMETERS = array(
|
||||
array(
|
||||
'keyword' => array(
|
||||
'name' => 'Filter by keyword',
|
||||
'title' => 'Enter the keyword to filter here'
|
||||
],
|
||||
'type' => [
|
||||
),
|
||||
'type' => array(
|
||||
'name' => 'Type of search',
|
||||
'title' => 'Choose between finding a boat or a crew',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Find a boat' => 'boats',
|
||||
'Find a crew' => 'crew'
|
||||
]
|
||||
],
|
||||
'status' => [
|
||||
)
|
||||
),
|
||||
'status' => array(
|
||||
'name' => 'Status on the boat',
|
||||
'title' => 'Choose between recreational or professional classified ads',
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'Recreational' => 'recreational',
|
||||
'Professional' => 'professional'
|
||||
]
|
||||
],
|
||||
'recreational_position' => [
|
||||
)
|
||||
),
|
||||
'recreational_position' => array(
|
||||
'name' => 'Recreational position wanted',
|
||||
'title' => 'Filter by recreational position you wanted aboard',
|
||||
'required' => false,
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'Amateur Crew' => 'Amateur Crew',
|
||||
'Friendship' => 'Friendship',
|
||||
@@ -43,14 +41,14 @@ class CrewbayBridge extends BridgeAbstract
|
||||
'Racing' => 'Racing',
|
||||
'Voluntary work' => 'Voluntary work',
|
||||
'Mile building' => 'Mile building'
|
||||
]
|
||||
],
|
||||
'professional_position' => [
|
||||
)
|
||||
),
|
||||
'professional_position' => array(
|
||||
'name' => 'Professional position wanted',
|
||||
'title' => 'Filter by professional position you wanted aboard',
|
||||
'required' => false,
|
||||
'type' => 'list',
|
||||
'values' => [
|
||||
'values' => array(
|
||||
'' => '',
|
||||
'1st Engineer' => '1st Engineer',
|
||||
'1st Mate' => '1st Mate',
|
||||
@@ -101,13 +99,12 @@ class CrewbayBridge extends BridgeAbstract
|
||||
'Stew/Masseuse' => 'Stew/Masseuse',
|
||||
'Manager' => 'Manager',
|
||||
'Sailing instructor' => 'Sailing instructor'
|
||||
]
|
||||
]
|
||||
]
|
||||
];
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData() {
|
||||
$url = $this->getURI();
|
||||
$html = getSimpleHTMLDOM($url) or returnClientError('No results for this query.');
|
||||
|
||||
@@ -121,9 +118,9 @@ class CrewbayBridge extends BridgeAbstract
|
||||
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];
|
||||
$positions = array($annonce->find('.title .position', 0)->plaintext);
|
||||
} else {
|
||||
$positions = [str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext)];
|
||||
$positions = array(str_replace('Wanted:', '', $annonce->find('.content li', 0)->plaintext));
|
||||
}
|
||||
} else {
|
||||
$list = $htmlDetail->find('.viewer-details .viewer-list');
|
||||
@@ -144,7 +141,7 @@ class CrewbayBridge extends BridgeAbstract
|
||||
}
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item = array();
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
$titleSelector = '.title h2';
|
||||
@@ -161,7 +158,7 @@ class CrewbayBridge extends BridgeAbstract
|
||||
|
||||
$item['uri'] = $detail->href;
|
||||
$images = $annonce->find('.avatar img');
|
||||
$item['enclosures'] = [end($images)->getAttribute('src')];
|
||||
$item['enclosures'] = array(end($images)->getAttribute('src'));
|
||||
|
||||
$content = $htmlDetail->find('.viewer-intro--info', 0)->innertext;
|
||||
|
||||
@@ -169,7 +166,7 @@ class CrewbayBridge extends BridgeAbstract
|
||||
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'])) {
|
||||
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;
|
||||
@@ -195,7 +192,7 @@ class CrewbayBridge extends BridgeAbstract
|
||||
$tags = $htmlDetail->find('li.viewer-tags--tag');
|
||||
foreach ($tags as $tag) {
|
||||
if (!isset($item['categories'])) {
|
||||
$item['categories'] = [];
|
||||
$item['categories'] = array();
|
||||
}
|
||||
$text = trim($tag->plaintext);
|
||||
if (!in_array($text, $item['categories'])) {
|
||||
@@ -206,14 +203,11 @@ class CrewbayBridge extends BridgeAbstract
|
||||
$this->items[] = $item;
|
||||
$limit += 1;
|
||||
|
||||
if ($limit == 10) {
|
||||
break;
|
||||
}
|
||||
if ($limit == 10) break;
|
||||
}
|
||||
}
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
public function getURI() {
|
||||
$uri = parent::getURI();
|
||||
|
||||
if ($this->getInput('type') == 'boats') {
|
||||
|
@@ -1,28 +1,25 @@
|
||||
<?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' => [
|
||||
const PARAMETERS = array( array(
|
||||
'n' => array(
|
||||
'name' => 'number of elements',
|
||||
'type' => 'number',
|
||||
'required' => true,
|
||||
'exampleValue' => 10
|
||||
]
|
||||
]];
|
||||
)
|
||||
));
|
||||
|
||||
public function getIcon()
|
||||
{
|
||||
public function getIcon() {
|
||||
return self::URI . '/favicon.ico';
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
public function collectData(){
|
||||
$html = getSimpleHTMLDOM(self::URI);
|
||||
|
||||
$number = $this->getInput('n');
|
||||
@@ -32,7 +29,7 @@ class CryptomeBridge extends BridgeAbstract
|
||||
$i = 0;
|
||||
foreach($html->find('pre', 1)->find('b') as $element) {
|
||||
foreach($element->find('a') as $element1) {
|
||||
$item = [];
|
||||
$item = array();
|
||||
$item['uri'] = $element1->href;
|
||||
$item['title'] = $element->plaintext;
|
||||
$this->items[] = $item;
|
||||
|
@@ -1,272 +0,0 @@
|
||||
<?php
|
||||
|
||||
class CssSelectorBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'ORelio';
|
||||
const NAME = 'CSS Selector Bridge';
|
||||
const URI = 'https://github.com/RSS-Bridge/rss-bridge/';
|
||||
const DESCRIPTION = 'Convert any site to RSS feed using CSS selectors (Advanced Users)';
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'home_page' => [
|
||||
'name' => 'Site URL: Home page with latest articles',
|
||||
'exampleValue' => 'https://example.com/blog/',
|
||||
'required' => true
|
||||
],
|
||||
'url_selector' => [
|
||||
'name' => 'Selector for article links or their parent elements',
|
||||
'title' => <<<EOT
|
||||
This bridge works using CSS selectors, e.g. "a.article" will match all <a class="article"
|
||||
href="URL">TITLE</a> on home page, each one being treated as a feed item.
|
||||
Instead of just a link you can selet one of its parent element. Everything inside that
|
||||
element becomes feed item content, e.g. image and summary present on home page.
|
||||
When doing so, the first link inside the selected element becomes feed item URL/Title.
|
||||
EOT,
|
||||
'exampleValue' => 'a.article',
|
||||
'required' => true
|
||||
],
|
||||
'url_pattern' => [
|
||||
'name' => '[Optional] Pattern for site URLs to keep in feed',
|
||||
'title' => 'Optionally filter items by applying a regular expression on their URL',
|
||||
'exampleValue' => '/blog/article/.*',
|
||||
],
|
||||
'content_selector' => [
|
||||
'name' => '[Optional] Selector to expand each article content',
|
||||
'title' => <<<EOT
|
||||
When specified, the bridge will fetch each article from its URL
|
||||
and extract content using the provided selector (Slower!)
|
||||
EOT,
|
||||
'exampleValue' => 'article.content',
|
||||
],
|
||||
'content_cleanup' => [
|
||||
'name' => '[Optional] Content cleanup: List of items to remove',
|
||||
'title' => 'Selector for unnecessary elements to remove inside article contents.',
|
||||
'exampleValue' => 'div.ads, div.comments',
|
||||
],
|
||||
'title_cleanup' => [
|
||||
'name' => '[Optional] Text to remove from expanded article title',
|
||||
'title' => <<<EOT
|
||||
When fetching each article page, feed item title comes from page title.
|
||||
Specify here some text from page title that need to be removed, e.g. " | BlogName".
|
||||
EOT,
|
||||
'exampleValue' => ' | BlogName',
|
||||
],
|
||||
'limit' => self::LIMIT
|
||||
]
|
||||
];
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
$url = $this->getInput('home_page');
|
||||
if (empty($url)) {
|
||||
$url = parent::getURI();
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (!empty($this->feedName)) {
|
||||
return $this->feedName;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = $this->getInput('home_page');
|
||||
$url_selector = $this->getInput('url_selector');
|
||||
$url_pattern = $this->getInput('url_pattern');
|
||||
$content_selector = $this->getInput('content_selector');
|
||||
$content_cleanup = $this->getInput('content_cleanup');
|
||||
$title_cleanup = $this->getInput('title_cleanup');
|
||||
$limit = $this->getInput('limit') ?? 10;
|
||||
|
||||
$html = defaultLinkTo(getSimpleHTMLDOM($url), $url);
|
||||
$this->feedName = $this->getPageTitle($html, $title_cleanup);
|
||||
$items = $this->htmlFindEntries($html, $url_selector, $url_pattern, $limit, $content_cleanup);
|
||||
|
||||
if (empty($content_selector)) {
|
||||
$this->items = $items;
|
||||
} else {
|
||||
foreach ($items as $item) {
|
||||
$this->items[] = $this->expandEntryWithSelector(
|
||||
$item['uri'],
|
||||
$content_selector,
|
||||
$content_cleanup,
|
||||
$title_cleanup,
|
||||
$item['title']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of URLs using a pattern and limit
|
||||
* @param array $links List of URLs
|
||||
* @param string $url_pattern Pattern to look for in URLs
|
||||
* @param int $limit Optional maximum amount of URLs to return
|
||||
* @return array Array of URLs
|
||||
*/
|
||||
protected function filterUrlList($links, $url_pattern, $limit = 0)
|
||||
{
|
||||
if (!empty($url_pattern)) {
|
||||
$url_pattern = '/' . str_replace('/', '\/', $url_pattern) . '/';
|
||||
$links = array_filter($links, function ($url) use ($url_pattern) {
|
||||
return preg_match($url_pattern, $url) === 1;
|
||||
});
|
||||
}
|
||||
|
||||
if ($limit > 0 && count($links) > $limit) {
|
||||
$links = array_slice($links, 0, $limit);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve title from webpage URL or DOM
|
||||
* @param string|object $page URL or DOM to retrieve title from
|
||||
* @param string $title_cleanup optional string to remove from webpage title, e.g. " | BlogName"
|
||||
* @return string Webpage title
|
||||
*/
|
||||
protected function getPageTitle($page, $title_cleanup = null)
|
||||
{
|
||||
if (is_string($page)) {
|
||||
$page = getSimpleHTMLDOMCached($page);
|
||||
}
|
||||
$title = html_entity_decode($page->find('title', 0)->plaintext);
|
||||
if (!empty($title)) {
|
||||
$title = trim(str_replace($title_cleanup, '', $title));
|
||||
}
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all elements from HTML content matching cleanup selector
|
||||
* @param string|object $content HTML content as HTML object or string
|
||||
* @return string|object Cleaned content (same type as input)
|
||||
*/
|
||||
protected function cleanArticleContent($content, $cleanup_selector)
|
||||
{
|
||||
$string_convert = false;
|
||||
if (is_string($content)) {
|
||||
$string_convert = true;
|
||||
$content = str_get_html($content);
|
||||
}
|
||||
|
||||
if (!empty($cleanup_selector)) {
|
||||
foreach ($content->find($cleanup_selector) as $item_to_clean) {
|
||||
$item_to_clean->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($string_convert) {
|
||||
$content = $content->outertext;
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve first N link+title+truncated-content from webpage URL or DOM satisfying the specified criteria
|
||||
* @param string|object $page URL or DOM to retrieve feed items from
|
||||
* @param string $url_selector DOM selector for matching links or their parent element
|
||||
* @param string $url_pattern Optional filter to keep only links matching the pattern
|
||||
* @param int $limit Optional maximum amount of URLs to return
|
||||
* @param string $content_cleanup Optional selector for removing elements, e.g. "div.ads, div.comments"
|
||||
* @return array of items {'uri': entry_url, 'title': entry_title, ['content': when present in DOM] }
|
||||
*/
|
||||
protected function htmlFindEntries($page, $url_selector, $url_pattern = '', $limit = 0, $content_cleanup = null)
|
||||
{
|
||||
if (is_string($page)) {
|
||||
$page = getSimpleHTMLDOM($page);
|
||||
}
|
||||
|
||||
$links = $page->find($url_selector);
|
||||
|
||||
if (empty($links)) {
|
||||
returnClientError('No results for URL selector');
|
||||
}
|
||||
|
||||
$link_to_item = [];
|
||||
foreach ($links as $link) {
|
||||
$item = [];
|
||||
if ($link->innertext != $link->plaintext) {
|
||||
$item['content'] = $link->innertext;
|
||||
}
|
||||
if ($link->tag != 'a') {
|
||||
$link = $link->find('a', 0);
|
||||
if (is_null($link)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
$item['uri'] = $link->href;
|
||||
$item['title'] = $link->plaintext;
|
||||
if (isset($item['content'])) {
|
||||
$item['content'] = convertLazyLoading($item['content']);
|
||||
$item['content'] = defaultLinkTo($item['content'], $item['uri']);
|
||||
$item['content'] = $this->cleanArticleContent($item['content'], $content_cleanup);
|
||||
}
|
||||
$link_to_item[$link->href] = $item;
|
||||
}
|
||||
|
||||
if (empty($link_to_item)) {
|
||||
returnClientError('The provided URL selector matches some elements, but they do not contain links.');
|
||||
}
|
||||
|
||||
$links = $this->filterUrlList(array_keys($link_to_item), $url_pattern, $limit);
|
||||
|
||||
if (empty($links)) {
|
||||
returnClientError('No results for URL pattern');
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($links as $link) {
|
||||
$items[] = $link_to_item[$link];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve article content from its URL using content selector and return a feed item
|
||||
* @param string $entry_url URL to retrieve article from
|
||||
* @param string $content_selector HTML selector for extracting content, e.g. "article.content"
|
||||
* @param string $content_cleanup Optional selector for removing elements, e.g. "div.ads, div.comments"
|
||||
* @param string $title_cleanup Optional string to remove from article title, e.g. " | BlogName"
|
||||
* @param string $title_default Optional title to use when could not extract title reliably
|
||||
* @return array Entry data: uri, title, content
|
||||
*/
|
||||
protected function expandEntryWithSelector($entry_url, $content_selector, $content_cleanup = null, $title_cleanup = null, $title_default = null)
|
||||
{
|
||||
if (empty($content_selector)) {
|
||||
returnClientError('Please specify a content selector');
|
||||
}
|
||||
|
||||
$entry_html = getSimpleHTMLDOMCached($entry_url);
|
||||
$article_content = $entry_html->find($content_selector);
|
||||
|
||||
if (!empty($article_content)) {
|
||||
$article_content = $article_content[0];
|
||||
} else {
|
||||
returnClientError('Could not find content selector at URL: ' . $entry_url);
|
||||
}
|
||||
|
||||
$article_content = convertLazyLoading($article_content);
|
||||
$article_content = defaultLinkTo($article_content, $entry_url);
|
||||
$article_content = $this->cleanArticleContent($article_content, $content_cleanup);
|
||||
|
||||
$article_title = $this->getPageTitle($entry_html, $title_cleanup);
|
||||
if (!empty($title_default) && (empty($article_title) || $article_title === $this->feedName)) {
|
||||
$article_title = $title_default;
|
||||
}
|
||||
|
||||
$item = [];
|
||||
$item['uri'] = $entry_url;
|
||||
$item['title'] = $article_title;
|
||||
$item['content'] = $article_content;
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -1,458 +0,0 @@
|
||||
<?php
|
||||
|
||||
class CssSelectorComplexBridge extends BridgeAbstract
|
||||
{
|
||||
const MAINTAINER = 'Lars Stegman';
|
||||
const NAME = 'CSS Selector Complex Bridge';
|
||||
const URI = 'https://github.com/RSS-Bridge/rss-bridge/';
|
||||
const DESCRIPTION = <<<EOT
|
||||
Convert any site to RSS feed using CSS selectors (Advanced Users). The bridge first selects
|
||||
the element describing the article entries. It then extracts the links to the articles from
|
||||
these elements. It then, depending on the setting "Load article from page", either parses
|
||||
the selected elements, or downloads the page for each article and parses those. Parsing the
|
||||
elements or page is done using the provided selectors.
|
||||
EOT;
|
||||
const PARAMETERS = [
|
||||
[
|
||||
'home_page' => [
|
||||
'name' => 'Site URL: Page with latest articles',
|
||||
'exampleValue' => 'https://example.com/blog/',
|
||||
'required' => true
|
||||
],
|
||||
'cookie' => [
|
||||
'name' => '[Optional] Cookie',
|
||||
'title' => <<<EOT
|
||||
Use when the website does not send the page contents, unless a static cookie is included.
|
||||
EOT,
|
||||
'exampleValue' => 'sessionId=deadb33f'
|
||||
],
|
||||
'title_cleanup' => [
|
||||
'name' => '[Optional] Text to remove from feed title',
|
||||
'title' => <<<EOT
|
||||
Text to remove from the feed title, which is read from the article list page.
|
||||
EOT,
|
||||
'exampleValue' => ' | BlogName',
|
||||
],
|
||||
'entry_element_selector' => [
|
||||
'name' => 'Selector for article entry elements',
|
||||
'title' => <<<EOT
|
||||
This bridge works using CSS selectors, e.g. "div.article" will match all
|
||||
<div class="article">...</div> on home page, each one being treated as a feed item.
|
||||
|
||||
Use the URL selector option to select the `a` element with the
|
||||
`href` to the article link. If this option is not configured, the first encountered
|
||||
`a` element is used.
|
||||
EOT,
|
||||
'exampleValue' => 'div.article',
|
||||
'required' => true
|
||||
],
|
||||
'url_selector' => [
|
||||
'name' => '[Optional] Selector for link elements',
|
||||
'title' => <<<EOT
|
||||
The selector to find `a` elements in the entry element. If empty,
|
||||
the first encountered `a` element is used. The `href` property
|
||||
is used to create entries in the feed.
|
||||
EOT,
|
||||
'exampleValue' => 'a.article',
|
||||
'defaultValue' => 'a'
|
||||
],
|
||||
'url_pattern' => [
|
||||
'name' => '[Optional] Pattern for site URLs to keep in feed',
|
||||
'title' => 'Optionally filter items by applying a regular expression on their URL',
|
||||
'exampleValue' => '/blog/article/.*',
|
||||
],
|
||||
'limit' => self::LIMIT,
|
||||
'use_article_pages' => [
|
||||
'name' => 'Load article from page',
|
||||
'title' => <<<EOT
|
||||
If true, the article page is load and parsed to get the article contents using
|
||||
the css selectors. (Slower!)
|
||||
Otherwise, the element selected by the article entry selector is used.
|
||||
EOT,
|
||||
'type' => 'checkbox'
|
||||
],
|
||||
'article_page_content_selector' => [
|
||||
'name' => '[Optional] Selector to select article element',
|
||||
'title' => 'Extract the article from its page using the provided selector',
|
||||
'exampleValue' => 'article.content',
|
||||
],
|
||||
'content_cleanup' => [
|
||||
'name' => '[Optional] Content cleanup: selector for items to remove',
|
||||
'title' => 'Selector for unnecessary elements to remove inside article contents.',
|
||||
'exampleValue' => 'div.ads, div.comments',
|
||||
],
|
||||
'title_selector' => [
|
||||
'name' => '[Optional] Selector for the article title',
|
||||
'title' => 'Selector to select the article title',
|
||||
'defaultValue' => 'h1'
|
||||
],
|
||||
'category_selector' => [
|
||||
'name' => '[Optional] Categories',
|
||||
'title' => <<<EOT
|
||||
Selector to extract the catgories the article has
|
||||
EOT,
|
||||
'exampleValue' => 'span.category, #main-category'
|
||||
],
|
||||
'author_selector' => [
|
||||
'name' => '[Optional] Author',
|
||||
'title' => <<<EOT
|
||||
Selector to extract the author of the article. If multiple elements are selected
|
||||
the first one is used.
|
||||
EOT,
|
||||
'exampleValue' => 'span#author'
|
||||
],
|
||||
'time_selector' => [
|
||||
'name' => '[Optional] Time selector',
|
||||
'title' => <<<EOT
|
||||
Selector to extract the timestamp of the article. If the element
|
||||
is an html5 `time` element, the value for the `datetime` attribute is used.
|
||||
EOT,
|
||||
],
|
||||
'time_format' => [
|
||||
'name' => '[Optional] Format string for parsing time',
|
||||
'title' => <<<EOT
|
||||
The format to use to parse the timestamp. See
|
||||
https://www.php.net/manual/en/datetimeimmutable.createfromformat.php
|
||||
for the format specification.
|
||||
EOT
|
||||
],
|
||||
'remove_styling' => [
|
||||
'name' => '[Optional] Remove styling',
|
||||
'title' => 'Remove class and style attributes from the page elements',
|
||||
'type' => 'checkbox'
|
||||
]
|
||||
]
|
||||
];
|
||||
|
||||
private $feedName = '';
|
||||
|
||||
public function getURI()
|
||||
{
|
||||
$url = $this->getInput('home_page');
|
||||
if (empty($url)) {
|
||||
$url = parent::getURI();
|
||||
}
|
||||
return $url;
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
if (!empty($this->feedName)) {
|
||||
return $this->feedName;
|
||||
}
|
||||
return parent::getName();
|
||||
}
|
||||
|
||||
protected function getHeaders()
|
||||
{
|
||||
$headers = [];
|
||||
$cookie = $this->getInput('cookie');
|
||||
if (!empty($cookie)) {
|
||||
$headers[] = 'Cookie: ' . $cookie;
|
||||
}
|
||||
|
||||
return $headers;
|
||||
}
|
||||
|
||||
public function collectData()
|
||||
{
|
||||
$url = $this->getInput('home_page');
|
||||
$headers = $this->getHeaders();
|
||||
|
||||
$entry_element_selector = $this->getInput('entry_element_selector');
|
||||
$url_selector = $this->getInput('url_selector');
|
||||
$url_pattern = $this->getInput('url_pattern');
|
||||
$limit = $this->getInput('limit') ?? 10;
|
||||
|
||||
$use_article_pages = $this->getInput('use_article_pages');
|
||||
$article_page_content_selector = $this->getInput('article_page_content_selector');
|
||||
$content_cleanup = $this->getInput('content_cleanup');
|
||||
$title_selector = $this->getInput('title_selector');
|
||||
$title_cleanup = $this->getInput('title_cleanup');
|
||||
$time_selector = $this->getInput('time_selector');
|
||||
$time_format = $this->getInput('time_format');
|
||||
|
||||
$category_selector = $this->getInput('category_selector');
|
||||
$author_selector = $this->getInput('author_selector');
|
||||
$remove_styling = $this->getInput('remove_styling');
|
||||
|
||||
$html = defaultLinkTo(getSimpleHTMLDOM($url, $headers), $url);
|
||||
$this->feedName = $this->getTitle($html, $title_cleanup);
|
||||
$entry_elements = $this->htmlFindEntryElements($html, $entry_element_selector, $url_selector, $url_pattern, $limit);
|
||||
|
||||
if (empty($entry_elements)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch the elements from the article pages.
|
||||
if ($use_article_pages) {
|
||||
if (empty($article_page_content_selector)) {
|
||||
returnClientError('`Article selector` is required when `Load article page` is enabled');
|
||||
}
|
||||
|
||||
foreach (array_keys($entry_elements) as $uri) {
|
||||
$entry_elements[$uri] = $this->fetchArticleElementFromPage($uri, $article_page_content_selector);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($entry_elements as $uri => $element) {
|
||||
$entry = $this->parseEntryElement(
|
||||
$element,
|
||||
$title_selector,
|
||||
$author_selector,
|
||||
$category_selector,
|
||||
$time_selector,
|
||||
$time_format,
|
||||
$content_cleanup,
|
||||
$this->feedName,
|
||||
$remove_styling
|
||||
);
|
||||
|
||||
$entry['uri'] = $uri;
|
||||
$this->items[] = $entry;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a list of URLs using a pattern and limit
|
||||
* @param array $links List of URLs
|
||||
* @param string $url_pattern Pattern to look for in URLs
|
||||
* @param int $limit Optional maximum amount of URLs to return
|
||||
* @return array Array of URLs
|
||||
*/
|
||||
protected function filterUrlList($links, $url_pattern, $limit = 0)
|
||||
{
|
||||
if (!empty($url_pattern)) {
|
||||
$url_pattern = '/' . str_replace('/', '\/', $url_pattern) . '/';
|
||||
$links = array_filter($links, function ($url) {
|
||||
return preg_match($url_pattern, $url) === 1;
|
||||
});
|
||||
}
|
||||
|
||||
if ($limit > 0 && count($links) > $limit) {
|
||||
$links = array_slice($links, 0, $limit);
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve title from webpage URL or DOM
|
||||
* @param string|object $page URL or DOM to retrieve title from
|
||||
* @param string $title_cleanup optional string to remove from webpage title, e.g. " | BlogName"
|
||||
* @return string Webpage title
|
||||
*/
|
||||
protected function getTitle($page, $title_cleanup)
|
||||
{
|
||||
if (is_string($page)) {
|
||||
$page = getSimpleHTMLDOMCached($page);
|
||||
}
|
||||
$title = html_entity_decode($page->find('title', 0)->plaintext);
|
||||
if (!empty($title)) {
|
||||
$title = trim(str_replace($title_cleanup, '', $title));
|
||||
}
|
||||
|
||||
return $title;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all elements from HTML content matching cleanup selector
|
||||
* @param string|object $content HTML content as HTML object or string
|
||||
* @return string|object Cleaned content (same type as input)
|
||||
*/
|
||||
protected function cleanArticleContent($content, $cleanup_selector, $remove_styling)
|
||||
{
|
||||
$string_convert = false;
|
||||
if (is_string($content)) {
|
||||
$string_convert = true;
|
||||
$content = str_get_html($content);
|
||||
}
|
||||
|
||||
if (!empty($cleanup_selector)) {
|
||||
foreach ($content->find($cleanup_selector) as $item_to_clean) {
|
||||
$item_to_clean->outertext = '';
|
||||
}
|
||||
}
|
||||
|
||||
if ($remove_styling) {
|
||||
foreach (['class', 'style'] as $attribute_to_remove) {
|
||||
foreach ($content->find('[' . $attribute_to_remove . ']') as $item_to_clean) {
|
||||
$item_to_clean->removeAttribute($attribute_to_remove);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($string_convert) {
|
||||
$content = $content->outertext;
|
||||
}
|
||||
return $content;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve first N link+element from webpage URL or DOM satisfying the specified criteria
|
||||
* @param string|object $page URL or DOM to retrieve feed items from
|
||||
* @param string $entry_selector DOM selector for matching HTML elements that contain article
|
||||
* entries
|
||||
* @param string $url_selector DOM selector for matching links
|
||||
* @param string $url_pattern Optional filter to keep only links matching the pattern
|
||||
* @param int $limit Optional maximum amount of URLs to return
|
||||
* @return array of items { <uri> => <html-element> }
|
||||
*/
|
||||
protected function htmlFindEntryElements($page, $entry_selector, $url_selector, $url_pattern = '', $limit = 0)
|
||||
{
|
||||
if (is_string($page)) {
|
||||
$page = getSimpleHTMLDOM($page);
|
||||
}
|
||||
|
||||
$entryElements = $page->find($entry_selector);
|
||||
if (empty($entryElements)) {
|
||||
returnClientError('No entry elements for entry selector');
|
||||
}
|
||||
|
||||
// Extract URIs with the associated entry element
|
||||
$links_with_elements = [];
|
||||
foreach ($entryElements as $entry) {
|
||||
$url_element = $entry->find($url_selector, 0);
|
||||
if (is_null($url_element)) {
|
||||
// No `a` element found in this entry
|
||||
if ($entry->tag == 'a') {
|
||||
$url_element = $entry;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$links_with_elements[$url_element->href] = $entry;
|
||||
}
|
||||
|
||||
if (empty($links_with_elements)) {
|
||||
returnClientError('The provided URL selector matches some elements, but they do not
|
||||
contain links.');
|
||||
}
|
||||
|
||||
// Filter using the URL pattern
|
||||
$filtered_urls = $this->filterUrlList(array_keys($links_with_elements), $url_pattern, $limit);
|
||||
|
||||
if (empty($filtered_urls)) {
|
||||
returnClientError('No results for URL pattern');
|
||||
}
|
||||
|
||||
$items = [];
|
||||
foreach ($filtered_urls as $link) {
|
||||
$items[$link] = $links_with_elements[$link];
|
||||
}
|
||||
|
||||
return $items;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Retrieve article element from its URL using content selector and return the DOM element
|
||||
* @param string $entry_url URL to retrieve article from
|
||||
* @param string $content_selector HTML selector for extracting content, e.g. "article.content"
|
||||
* @return article DOM element
|
||||
*/
|
||||
protected function fetchArticleElementFromPage($entry_url, $content_selector)
|
||||
{
|
||||
$entry_html = getSimpleHTMLDOMCached($entry_url);
|
||||
$article_content = $entry_html->find($content_selector, 0);
|
||||
|
||||
if (is_null($article_content)) {
|
||||
returnClientError('Could not article content at URL: ' . $entry_url);
|
||||
}
|
||||
|
||||
$article_content = defaultLinkTo($article_content, $entry_url);
|
||||
return $article_content;
|
||||
}
|
||||
|
||||
protected function parseTimeStrAsTimestamp($timeStr, $format)
|
||||
{
|
||||
$date = date_parse_from_format($format, $timeStr);
|
||||
if ($date['error_count'] != 0) {
|
||||
returnClientError('Error while parsing time string');
|
||||
}
|
||||
|
||||
$timestamp = mktime(
|
||||
$date['hour'],
|
||||
$date['minute'],
|
||||
$date['second'],
|
||||
$date['month'],
|
||||
$date['day'],
|
||||
$date['year']
|
||||
);
|
||||
|
||||
if ($timestamp == false) {
|
||||
returnClientError('Error while creating timestamp');
|
||||
}
|
||||
|
||||
return $timestamp;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve article content from its URL using content selector and return a feed item
|
||||
* @param object $entry_html A DOM element containing the article
|
||||
* @param string $title_selector A selector to the article title from the article
|
||||
* @param string $author_selector A selector to find the article author
|
||||
* @param string $time_selector A selector to get the article publication time.
|
||||
* @param string $time_format The format to parse the time_selector.
|
||||
* @param string $content_cleanup Optional selector for removing elements, e.g. "div.ads,
|
||||
* div.comments"
|
||||
* @param string $title_default Optional title to use when could not extract title reliably
|
||||
* @param bool $remove_styling Whether to remove class and style attributes from the HTML
|
||||
* @return array Entry data: uri, title, content
|
||||
*/
|
||||
protected function parseEntryElement(
|
||||
$entry_html,
|
||||
$title_selector = null,
|
||||
$author_selector = null,
|
||||
$category_selector = null,
|
||||
$time_selector = null,
|
||||
$time_format = null,
|
||||
$content_cleanup = null,
|
||||
$title_default = null,
|
||||
$remove_styling = false
|
||||
) {
|
||||
$article_content = convertLazyLoading($entry_html);
|
||||
|
||||
if (is_null($title_selector)) {
|
||||
$article_title = $title_default;
|
||||
} else {
|
||||
$article_title = trim($entry_html->find($title_selector, 0)->innertext);
|
||||
}
|
||||
|
||||
$author = null;
|
||||
if (!is_null($author_selector) && $author_selector != '') {
|
||||
$author = trim($entry_html->find($author_selector, 0)->innertext);
|
||||
}
|
||||
|
||||
$categories = [];
|
||||
if (!is_null($category_selector && $category_selector != '')) {
|
||||
$category_elements = $entry_html->find($category_selector);
|
||||
foreach ($category_elements as $category_element) {
|
||||
$categories[] = trim($category_element->innertext);
|
||||
}
|
||||
}
|
||||
|
||||
$time = null;
|
||||
if (!is_null($time_selector) && $time_selector != '') {
|
||||
$time_element = $entry_html->find($time_selector, 0);
|
||||
$time = $time_element->getAttribute('datetime');
|
||||
if (is_null($time)) {
|
||||
$time = $time_element->innertext;
|
||||
}
|
||||
|
||||
$this->parseTimeStrAsTimestamp($time, $time_format);
|
||||
}
|
||||
|
||||
$article_content = $this->cleanArticleContent($article_content, $content_cleanup, $remove_styling);
|
||||
|
||||
$item = [];
|
||||
$item['title'] = $article_title;
|
||||
$item['content'] = $article_content;
|
||||
$item['categories'] = $categories;
|
||||
$item['timestamp'] = $time;
|
||||
$item['author'] = $author;
|
||||
return $item;
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user