1
0
mirror of https://github.com/RSS-Bridge/rss-bridge.git synced 2025-08-17 14:00:43 +02:00

Compare commits

..

92 Commits

Author SHA1 Message Date
Eugene Molotov
b24b5ed3ee Bump version to dev.2021-04-25 2021-04-25 15:32:35 +05:00
Eugene Molotov
f06a8ae307 [README] Update list of contributors 2021-04-25 15:30:17 +05:00
dawidsowa
4f7ef212b7 [RedditBridge] Add detectParameters (#2070) 2021-04-19 22:17:36 +05:00
dawidsowa
13e9a96cf3 [RedditBridge]: Add score filter (#2045) 2021-04-19 22:14:35 +05:00
ORelio
00a24a98be [NyaaTorrents] Rewrite as Feed Expander (#2073)
NyaaTorrents allows search criteria as URL parameters in RSS feed so we just need to expand feed items
2021-04-19 21:59:51 +05:00
FiveFilters.org
76c38332ee [TwitterBridge] Improve timeline processing for username mode (#1946) 2021-04-12 23:08:38 +05:00
Joseph
65be209a47 [TwitScoopBridge] Remove less than (<) character from item title (#2034) 2021-04-12 23:01:46 +05:00
sysadminstory
146639ffc9 [ZoneTelechargement] Update unprotected URL and Feed URL (#2065)
The unproteced URL has changed again, and has been updated.

The RSS Feed URL is now a link to the Show page and not to the Homepage anymore.
2021-04-12 23:01:09 +05:00
sysadminstory
e1c19461ca [ExtremeDownloadBridge] Feed URL updated (#2066)
The Feed URL is now a link to the TV Show and not the Homepage !
2021-04-12 22:59:16 +05:00
Harvey Christian Pacleb
ff0c7a9013 [GenshinImpactBridge] Use Asia/Shanghai time zone for article dates (#2040) 2021-04-10 13:35:34 +05:00
ORelio
b754d14698 [FeedExpander] Handle Atom enclosures (#2039) 2021-04-04 15:21:15 +05:00
sysadminstory
3423b3bbe1 [ZoneTelechargement] Change URL load method (#2044) 2021-04-04 14:31:48 +05:00
Joseph
5966cc0a9c [TheFarSideBridge] Add bridge (#1484) 2021-04-03 23:31:49 +05:00
Joseph
579bfa669c [WallmineNewsBridge] Add bridge (#2035) 2021-04-02 18:01:51 +05:00
ORelio
d61871a45e [NyaaTorrents] Allow searching by username (#2033) 2021-03-31 10:59:31 +05:00
Eugene Molotov
0c8fabeb11 [PikabuBridge] Marking posts from "Как бы Новости" section, which are funny and deliberately fake (#2032) 2021-03-30 23:06:23 +05:00
Matthieu Rakotojaona
40c84b5dc3 [HackerNewsUserThreads] New bridge (#1902) 2021-03-30 21:56:17 +05:00
Yaman Qalieh
6f75d07456 [GitHubPullRequestBridge] Add new bridge inheriting GithubIssueBridge (#2001) 2021-03-29 22:15:56 +05:00
guigot
b4f809aa44 [MondeDiploBridge] Fix blog article uri (#1961) 2021-03-25 22:15:02 +05:00
Joseph
bcecd70df7 [DownDetectorBridge] Fix bridge (#1957) 2021-03-25 22:02:45 +05:00
Joseph
a6c0874b9a [TwitScoopBridge] Fix encoding of less than character (<) (#2023) 2021-03-21 11:39:01 +05:00
somini
9e6f063cfd [PresidenciaPT]: New Bridge (#2016) 2021-03-17 21:30:47 +05:00
Joseph
f904353fd2 [InternetArchiveBridge] Fix collection links (#1551) 2021-03-16 18:07:04 +05:00
Andrea Draghetti
3aafd44079 [TelegramBridge] Display the name of the attachments (#2003)
Sometimes attachments are posted in Telegram channels without any text. 

The script recognizes a new message but does not report any text, with this commit the file names will also be included in the RSS Feed.
2021-03-16 18:04:07 +05:00
Joseph
75b85f61e7 [BandcampBridge] Fix title extraction on empty band pages (#1966) 2021-03-16 18:00:26 +05:00
Joseph
07e1e8497c [DockerHubBridge] Add detectParameters() (#1996) 2021-03-15 21:54:26 +05:00
Joseph
700813e924 [FirefoxAddonsBridge] Add detectParameters() (#1997) 2021-03-15 21:27:53 +05:00
Joseph
5c011c8d90 [TwitScoopBridge] Add bridge (#2018) 2021-03-15 21:20:02 +05:00
Joseph
8d0d08a4d8 [YeggiBridge] Fix lint error (#2019) 2021-03-15 21:15:01 +05:00
Antoine Turmel
55548dcb5f [YeggiBridge] New bridge (#1910) 2021-03-13 21:57:30 +05:00
Justin Goette
0217b270a7 [README] Fix typo (#2004) 2021-03-10 22:13:41 +05:00
Joseph
2ed34f5ebe [FirefoxAddonsBridge] Set unique id for items (#2007)
Adds unique id for each item using item title.
2021-03-09 11:16:56 +05:00
csisoap
2448ed41c9 [ReutersBridge] Add new wireitem template type (#2006)
and retain the list of parameters
2021-03-09 11:15:48 +05:00
Joseph
b25674b3a0 [HtmlFormat] Use str_ireplace() when creating feed format URLs (#2008)
Fixes feed format URLs not being created with correct format value if html feed URL uses a lowercase format value.
2021-03-08 12:17:12 +05:00
Joseph
2ce1a6365b [README] Update build status badges (#1995) 2021-02-28 18:41:03 +05:00
Joseph
30aeeb2a0c [DockerHubBridge] Add support for official images (#1999) 2021-02-28 18:26:24 +05:00
Joseph
c294a652a3 [TelegramBridge] Add detectParameters() (#1998) 2021-02-28 18:20:44 +05:00
Park0
a5f2175531 [SymfonyCasts] Added new bridge (#2000) 2021-02-28 18:17:54 +05:00
dawidsowa
569276f4ef [RedditBridge]: Add user option (#1943) 2021-02-23 12:08:43 +05:00
Joseph
687eb728d4 [DockerHubBridge] Fix bridge name (#1994) 2021-02-22 15:03:04 +00:00
Monocularity
0521ba5873 [NordbayernBridge] Fixed typo of region "Hilpoltstein" (#1962) 2021-02-21 16:43:23 +00:00
Joseph
3d642971c0 [FirefoxAddonsBridge] Add bridge (#1952) 2021-02-21 17:30:01 +05:00
Shikiryu
8f086169cc [TheYeteeBridge] Fix HTML parsing (#1986) 2021-02-21 15:19:20 +05:00
Joseph
ce34e7eb89 [DockerhubBridge] Add bridge (#1990) 2021-02-21 15:17:07 +05:00
sysadminstory
ee5d190391 [RadioMelodieBridge] Fix header image (#1985)
Header Image is now using a direct link to the image, but without the
website base URL : the bridge now sends the right URL.
2021-02-17 10:07:35 +05:00
Eugene Molotov
98352845a1 [VkBridge] Remove non ascii chars from post date to correctly parse it (#1977) 2021-02-10 18:11:48 +05:00
Joseph
9e58735b01 [FormatFactory] Ignore case in format values (#1967) 2021-02-09 18:13:03 +05:00
Thomas
771b851b52 [contents.php] Fix logical error in getSimpleHTMLDOMCached function (#1974)
Previously content was only loaded from cache when debug mode was enabled (the opposite of the expected behavior)
2021-02-09 17:40:16 +05:00
Joseph
809343ed06 [SoundcloudBridge] Fix client ID extraction (#1973) 2021-02-09 17:33:14 +05:00
Lyra
e9424f6a08 Merge branch 'master' of github.com:RSS-Bridge/rss-bridge 2021-02-07 14:44:36 +01:00
Lyra
e5846c03ba Attempt to fix LeBonCoinBridge 2021-02-07 14:44:28 +01:00
Joseph
6224fbb6a2 [BridgeCard] Display configuration options (if enabled) when bridge has no parameters (#1968)
Updates displayBridgeCard() in BridgeCard to allow configuration options noproxy and cache_timeout to be displayed, if enabled, when a bridge has no parameters in its PARAMETERS array
2021-02-05 10:17:30 +05:00
sysadminstory
eab575dc9d [ZoneTelechargement] Update Direct Download Unprotected URL (#1963) 2021-01-30 18:02:36 +00:00
André Andersson
b56637c833 [BukowskisBridge] Add bridge (#1927) 2021-01-29 23:36:45 +05:00
Simon816
005b22701d [FSecureBlogBridge] Add bridge (#1932) 2021-01-29 23:27:35 +05:00
hollowleviathan
43b7621f45 [ReutersBridge] Add bridge (#1653) 2021-01-29 22:57:40 +05:00
Joseph
ea289a0cea [GithubIssueBridge] Fix issue id and comment id extraction (#1950) 2021-01-27 11:06:59 +05:00
Joseph
43acb555e0 [ChristianDailyReporterBridge] Remove bridge (#1948)
> The time has come for the Christian Daily Reporter to ride off into the sunset. Disrn is here now and it delivers everything CDR did, times ten.

https://christiandailyreporter.com/
2021-01-22 10:20:17 +05:00
Eugene Molotov
3b7e61fb55 [Arte7Bridge] Mitsu is no longer maintainer of the bridge
> That said, please also feel free to remove me as "maintainer" since that's simply no longer reality

https://github.com/RSS-Bridge/rss-bridge/issues/1906#issuecomment-765015048
2021-01-22 09:52:07 +05:00
Corentin Garcia
fbbd6a02c6 [DribbbleBridge] Fix pictures parsing (#1911) 2021-01-20 22:26:15 +05:00
sysadminstory
3534193032 [ZoneTelechargement] Update URL and fix typos (#1936) 2021-01-17 16:32:59 +05:00
sysadminstory
81fc8c89d4 [ExtremeDownloadBridge] Update URL (#1937) 2021-01-17 16:31:44 +05:00
Eugene Molotov
ea1de07fe5 [CI] Fix non-working phpcompatibility job (#1928) 2021-01-12 12:02:49 +05:00
Eugene Molotov
2de5ce8387 [CI] Fix phpcompatibility job running wrong scenario 2021-01-12 11:00:38 +05:00
Eugene Molotov
28f9215913 [CI] Replace Travis CI with Github Actions (#1926)
Travis stopped working again and no answers from support
2021-01-11 22:39:15 +05:00
FiveFilters.org
f927781750 [TwitterBridge] Add option to hide pinned tweet (#1908) 2021-01-10 13:50:06 +05:00
Jacques Heunis
e128ce807a [ItchioBridge] Add bridge (#1918) 2021-01-10 13:30:12 +05:00
Derrick Lee
6b870f0c3e [TwitterBridge] Fix username matching to be case insensitive with noretweet option (#1924) 2021-01-10 13:19:38 +05:00
JimDog546
5ed161943c [InstagramBridge] Remove redundant data collection for sidecar and video (#1920)
getInstagramSidecarData and getInstagramVideoData were unnecessarily calling getSinglePostData to retrieve data already present in collectData's call of getInstagramJSON. getSinglePostData sometimes doesn't retrieve data properly resulting in incomplete post information. Since the information needed is already present, pass it from collectData instead, eliminating the redundant data collection and improving reliability.
2021-01-10 13:14:58 +05:00
Corentin Garcia
1edec1aa45 [GenshinImpactBridge] Add bridge (#1912) 2020-12-30 23:30:02 +05:00
dawidsowa
3c285d50ec [RedditBridge] Rewrite to use JSON (#1781) 2020-12-23 22:42:15 +05:00
Clemens Neubauer
3aae00b56a [HDWallpapersBridge] Fix URLs (#1892) 2020-12-23 22:19:32 +05:00
sysadminstory
21798e8228 [ZoneTelechargement] Add support for Streaming links (#1858) 2020-12-23 22:13:10 +05:00
Eugene Molotov
3226a5e31e [core] Use more correct text to indicate errored response from upstream 2020-12-23 17:49:11 +05:00
Eugene Molotov
59bbc9d2e7 [VkBridge] Several improvements (#1802)
* Improved post author computation
* Show repost sources
* Handle second copy quote
* Fixed incorrect image uri's
2020-12-18 07:58:51 +05:00
Eugene Molotov
2ddd357a62 [JS] Fixed TypeError: textValue is null 2020-12-18 07:51:33 +05:00
Devon Hess
c302bca1e6 [IKWYDBridge] New bridge (#1874) 2020-12-13 16:08:25 +05:00
t0stiman
0b3082609d [RaceDepartmentBridge] added a bridge for racedepartment.com (#1681) 2020-12-13 15:50:54 +05:00
Lyra
810a2503c9 [core] Add configuration for bridges, allowing private bridges (#1343) 2020-12-12 21:05:22 +05:00
Eugene Molotov
56b2c516e4 [InstagramBridge] pauder is defacto not a maintainer of InstagramBridge 2020-12-09 10:47:48 +05:00
Eugene Molotov
fc81bed717 [BridgeAbstract] Correct getIcon method 2020-12-07 21:08:58 +04:00
Tobias Alexander Franke
56eb829a66 [EconomistBridge] Fixes for fetching new page structure (#1836) 2020-11-29 15:31:20 +05:00
Alex Kirk
7705d097e3 [SkimfeedBridge] Add parameter detection (#1877) 2020-11-29 15:28:07 +05:00
Peter Dave Hello
be9df41e07 [Dockerfile] Clean up apt cache, make image smaller (#1880) 2020-11-29 15:18:20 +05:00
Joseph
1e75f9d3d5 [ReporterreBridge + KernelBugTrackerBridge + BastaBridge] Use defaultLinkTo() (#1862) 2020-11-23 23:49:25 +05:00
Eugene Molotov
0755181555 [CeskaTelevizeBridge] Remove executable flag from bridge file 2020-11-19 16:19:46 +05:00
Joseph
e6c73a1fe3 [FlickrBridge] Add filter by media and sort by options (#1758) 2020-11-16 22:33:48 +05:00
David Pedersen
5729e069e9 [AppleMusicBridge] Use title from website (#1855) 2020-11-16 22:15:39 +05:00
David Pedersen
0b494d9c0e [AmazonPriceTrackerBridge] Add support for Swedish Amazon (#1856) 2020-11-16 22:13:23 +05:00
Eugene Molotov
c855d5089f Revert "[TelegramBridge] Prevent double encoding entities (#1182)"
This reverts commit 5e2f0fb626.
2020-11-11 23:31:24 +05:00
dawidsowa
6baf64f29e [Rule34pahealBridge] Use full size image (#1775) 2020-11-11 23:28:12 +05:00
Alex Balgavy
7d4b76be99 [SeznamZpravyBridge] New bridge (#1806) 2020-11-11 22:39:34 +05:00
66 changed files with 2848 additions and 6487 deletions

35
.github/workflows/lint.yml vendored Normal file
View File

@@ -0,0 +1,35 @@
name: Lint
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
phpcs:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['5.6', '7.0', '7.1', '7.2', '7.3', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
tools: phpcs
- run: phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p
phpcompatibility:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['5.6', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require dealerdirect/phpcodesniffer-composer-installer
- run: composer global require phpcompatibility/php-compatibility
- run: ~/.composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --warning-severity=0 --extensions=php -p

47
.github/workflows/tests.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: Tests
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
phpunit6:
runs-on: ubuntu-16.04
strategy:
matrix:
php-versions: ['7.0', '7.1', '7.2']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^6
- run: phpunit --configuration=phpunit.xml --include-path=lib/
phpunit7:
runs-on: ubuntu-16.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-16.04
strategy:
matrix:
php-versions: ['7.3', '7.4']
steps:
- uses: actions/checkout@v2
- uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php-versions }}
- run: composer global require phpunit/phpunit ^8
- run: phpunit --configuration=phpunit.xml --include-path=lib/

View File

@@ -1,46 +0,0 @@
dist: trusty
language: php
install:
- composer global require dealerdirect/phpcodesniffer-composer-installer;
- composer global require phpcompatibility/php-compatibility;
- if [[ "$PHPUNIT" ]]; then
composer global require phpunit/phpunit ^$PHPUNIT;
fi
script:
- phpenv rehash
# Run PHP_CodeSniffer on all versions
- ~/.config/composer/vendor/bin/phpcs . --standard=phpcs.xml --warning-severity=0 --extensions=php -p;
# Check PHP compatibility for the lowest and highest supported version
- if [[ $TRAVIS_PHP_VERSION == "5.6" || $TRAVIS_PHP_VERSION == "7.3" ]]; then
~/.config/composer/vendor/bin/phpcs . --standard=phpcompatibility.xml --extensions=php -p;
fi
# Run unit tests on highest major version
- if [[ ${TRAVIS_PHP_VERSION:0:1} == "7" ]]; then
~/.config/composer/vendor/bin/phpunit --configuration=phpunit.xml --include-path=lib/;
fi
php:
- 7.3
env:
- PHPUNIT=6
- PHPUNIT=7
- PHPUNIT=8
matrix:
fast_finish: true
include:
- php: 5.6
env: PHPUNIT=
- php: 7.0
- php: 7.1
- php: 7.2
allow_failures:
- php: 7.3
env: PHPUNIT=7
- php: 7.3
env: PHPUNIT=8

View File

@@ -7,6 +7,7 @@ RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" \
&& 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 \

View File

@@ -1,6 +1,6 @@
![RSS-Bridge](static/logo_600px.png)
===
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Build Status](https://travis-ci.org/RSS-Bridge/rss-bridge.svg?branch=master)](https://travis-ci.org/RSS-Bridge/rss-bridge) [![Docker Build Status](https://img.shields.io/docker/build/rssbridge/rss-bridge.svg?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
[![LICENSE](https://img.shields.io/badge/license-UNLICENSE-blue.svg)](UNLICENSE) [![GitHub release](https://img.shields.io/github/release/rss-bridge/rss-bridge.svg?logo=github)](https://github.com/rss-bridge/rss-bridge/releases/latest) [![Debian Release](https://img.shields.io/badge/dynamic/json.svg?logo=debian&label=debian%20release&url=https%3A%2F%2Fsources.debian.org%2Fapi%2Fsrc%2Frss-bridge%2F&query=%24.versions%5B0%5D.version&colorB=blue)](https://tracker.debian.org/pkg/rss-bridge) [![Guix Release](https://img.shields.io/badge/guix%20release-unknown-blue.svg)](https://www.gnu.org/software/guix/packages/R/) [![Actions Status](https://img.shields.io/github/workflow/status/RSS-Bridge/rss-bridge/Tests/master?label=GitHub%20Actions&logo=github)](https://github.com/RSS-Bridge/rss-bridge/actions) [![Docker Build Status](https://img.shields.io/docker/cloud/build/rssbridge/rss-bridge?logo=docker)](https://hub.docker.com/r/rssbridge/rss-bridge/)
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.
@@ -114,6 +114,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [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)
@@ -127,13 +128,14 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [AxorPL](https://github.com/AxorPL)
* [ayacoo](https://github.com/ayacoo)
* [az5he6ch](https://github.com/az5he6ch)
* [azdkj532](https://github.com/azdkj532)
* [b1nj](https://github.com/b1nj)
* [benasse](https://github.com/benasse)
* [Binnette](https://github.com/Binnette)
* [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)
@@ -142,6 +144,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [da2x](https://github.com/da2x)
* [Daiyousei](https://github.com/Daiyousei)
* [dawidsowa](https://github.com/dawidsowa)
* [DevonHess](https://github.com/DevonHess)
* [disk0x](https://github.com/disk0x)
* [DJCrashdummy](https://github.com/DJCrashdummy)
* [Djuuu](https://github.com/Djuuu)
@@ -149,10 +152,13 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [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)
* [em92](https://github.com/em92)
* [eMerzh](https://github.com/eMerzh)
* [EtienneM](https://github.com/EtienneM)
* [fanch317](https://github.com/fanch317)
* [fivefilters](https://github.com/fivefilters)
* [floviolleau](https://github.com/floviolleau)
* [fluffy-critter](https://github.com/fluffy-critter)
* [Frenzie](https://github.com/Frenzie)
@@ -165,16 +171,21 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [griffaurel](https://github.com/griffaurel)
* [Grummfy](https://github.com/Grummfy)
* [gsantner](https://github.com/gsantner)
* [guigot](https://github.com/guigot)
* [hollowleviathan](https://github.com/hollowleviathan)
* [hpacleb](https://github.com/hpacleb)
* [hunhejj](https://github.com/hunhejj)
* [husim0](https://github.com/husim0)
* [IceWreck](https://github.com/IceWreck)
* [j0k3r](https://github.com/j0k3r)
* [JackNUMBER](https://github.com/JackNUMBER)
* [jannyba](https://github.com/jannyba)
* [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)
* [Jocker666z](https://github.com/Jocker666z)
* [johnnygroovy](https://github.com/johnnygroovy)
* [johnpc](https://github.com/johnpc)
@@ -204,6 +215,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [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)
@@ -222,13 +234,17 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [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)
@@ -238,6 +254,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [rremizov](https://github.com/rremizov)
* [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)
* [squeek502](https://github.com/squeek502)
@@ -247,6 +264,7 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [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)
@@ -254,12 +272,14 @@ https://gist.github.com/LogMANOriginal/da00cd1e5f0ca31cef8e193509b17fd8
* [ThePadawan](https://github.com/ThePadawan)
* [TheRadialActive](https://github.com/TheRadialActive)
* [theScrabi](https://github.com/theScrabi)
* [thezeroalpha](https://github.com/thezeroalpha)
* [TitiTestScalingo](https://github.com/TitiTestScalingo)
* [triatic](https://github.com/triatic)
* [VerifiedJoseph](https://github.com/VerifiedJoseph)
* [WalterBarrett](https://github.com/WalterBarrett)
* [wtuuju](https://github.com/wtuuju)
* [xurxof](https://github.com/xurxof)
* [yamanq](https://github.com/yamanq)
* [yardenac](https://github.com/yardenac)
* [ymeister](https://github.com/ymeister)
* [ZeNairolf](https://github.com/ZeNairolf)
@@ -293,6 +313,6 @@ You're not social when you hamper sharing by removing feeds. You're happy to hav
We want to share with friends, using open protocols: RSS, Atom, XMPP, whatever. Because no one wants to have *your* service with *your* applications using *your* API force-feeding them. Friends must be free to choose whatever software and service they want.
We are rebuilding bridges you have wilfully destroyed.
We are rebuilding bridges you have willfully destroyed.
Get your shit together: Put RSS/Atom back in.

View File

@@ -131,6 +131,7 @@ class DisplayAction extends ActionAbstract {
try {
$bridge->setDatas($bridge_params);
$bridge->loadConfiguration();
$bridge->collectData();
$items = $bridge->getItems();

View File

@@ -32,6 +32,7 @@ class AmazonPriceTrackerBridge extends BridgeAbstract {
'Mexico' => 'com.mx',
'Netherlands' => 'nl',
'Spain' => 'es',
'Sweden' => 'se',
'United Kingdom' => 'co.uk',
'United States' => 'com',
),

View File

@@ -20,6 +20,8 @@ class AppleMusicBridge extends BridgeAbstract {
));
const CACHE_TIMEOUT = 21600; // 6 hours
private $title;
public function collectData() {
$url = $this->getInput('url');
$html = getSimpleHTMLDOM($url)
@@ -27,6 +29,8 @@ class AppleMusicBridge extends BridgeAbstract {
$imgSize = $this->getInput('imgSize');
$this->title = $html->find('title', 0)->innertext;
// Grab the json data from the page
$html = $html->find('script[id=shoebox-ember-data-store]', 0);
$html = strstr($html, '{');
@@ -59,4 +63,8 @@ class AppleMusicBridge extends BridgeAbstract {
return $a['timestamp'] < $b['timestamp'];
});
}
public function getName() {
return $this->title ?: parent::getName();
}
}

View File

@@ -1,7 +1,7 @@
<?php
class Arte7Bridge extends BridgeAbstract {
const MAINTAINER = 'mitsukarenai';
// const MAINTAINER = 'mitsukarenai';
const NAME = 'Arte +7';
const URI = 'https://www.arte.tv/';
const CACHE_TIMEOUT = 1800; // 30min

View File

@@ -125,9 +125,11 @@ class BandcampBridge extends BridgeAbstract {
case 'By album':
$html = getSimpleHTMLDOMCached($this->getURI(), 86400);
$titleElement = $html->find('head meta[name=title]', 0)
or returnServerError('Unable to find title on: ' . $this->getURI());
$this->feedName = $titleElement->content;
if ($html->find('meta[name=title]', 0)) {
$this->feedName = $html->find('meta[name=title]', 0)->content;
} else {
$this->feedName = str_replace('Music | ', '', $html->find('title', 0)->plaintext);
}
$regex = '/band_id=(\d+)/';
if(preg_match($regex, $html, $matches) == false)

View File

@@ -19,13 +19,11 @@ class BastaBridge extends BridgeAbstract {
$item['title'] = $element->find('title', 0)->innertext;
$item['uri'] = $element->find('guid', 0)->plaintext;
$item['timestamp'] = strtotime($element->find('dc:date', 0)->plaintext);
// Replaces all relative image URLs by absolute URLs.
// Relative URLs always start with 'local/'!
$item['content'] = preg_replace(
'/src=["\']{1}([^"\']+)/ims',
'src=\'' . self::URI . '$1\'',
getSimpleHTMLDOM($item['uri'])->find('div.texte', 0)->innertext
);
$html = getSimpleHTMLDOM($item['uri']);
$html = defaultLinkTo($html, self::URI);
$item['content'] = $html->find('div.texte', 0)->innertext;
$this->items[] = $item;
$limit++;
}

219
bridges/BukowskisBridge.php Executable file
View File

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

0
bridges/CeskaTelevizeBridge.php Executable file → Normal file
View File

View File

@@ -1,28 +0,0 @@
<?php
class ChristianDailyReporterBridge extends BridgeAbstract {
const MAINTAINER = 'rogerdc';
const NAME = 'Christian Daily Reporter Unofficial RSS';
const URI = 'https://www.christiandailyreporter.com/';
const DESCRIPTION = 'The Unofficial Christian Daily Reporter RSS';
// const CACHE_TIMEOUT = 86400; // 1 day
public function getIcon() {
return self::URI . 'images/cdrfavicon.png';
}
public function collectData() {
$uri = 'https://www.christiandailyreporter.com/';
$html = getSimpleHTMLDOM($uri)
or returnServerError('Could not request Christian Daily Reporter.');
foreach($html->find('div.top p a,div.column p a') as $element) {
$item = array();
// Title
$item['title'] = $element->innertext;
// URL
$item['uri'] = $element->href;
$this->items[] = $item;
}
}
}

166
bridges/DockerHubBridge.php Normal file
View File

@@ -0,0 +1,166 @@
<?php
class DockerHubBridge extends BridgeAbstract {
const NAME = 'Docker Hub Bridge';
const URI = 'https://hub.docker.com';
const DESCRIPTION = 'Returns new images for a container';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
'User Submitted Image' => array(
'user' => array(
'name' => 'User',
'type' => 'text',
'required' => true,
'exampleValue' => 'rssbridge',
),
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'required' => true,
'exampleValue' => 'rss-bridge',
)
),
'Official Image' => array(
'repo' => array(
'name' => 'Repository',
'type' => 'text',
'required' => true,
'exampleValue' => 'postgres',
)
),
);
const CACHE_TIMEOUT = 3600; // 1 hour
private $apiURL = 'https://hub.docker.com/v2/repositories/';
private $imageUrlRegex = '/hub\.docker\.com\/r\/([\w]+)\/([\w-]+)\/?/';
private $officialImageUrlRegex = '/hub\.docker\.com\/_\/([\w-]+)\/?/';
public function detectParameters($url) {
$params = array();
// user submitted image
if(preg_match($this->imageUrlRegex, $url, $matches)) {
$params['context'] = 'User Submitted Image';
$params['user'] = $matches[1];
$params['repo'] = $matches[2];
return $params;
}
// official image
if(preg_match($this->officialImageUrlRegex, $url, $matches)) {
$params['context'] = 'Official Image';
$params['repo'] = $matches[1];
return $params;
}
return null;
}
public function collectData() {
$json = getContents($this->getApiUrl())
or returnServerError('Could not request: ' . $this->getURI());
$data = json_decode($json, false);
foreach ($data->results as $result) {
$item = array();
$lastPushed = date('Y-m-d H:i:s', strtotime($result->tag_last_pushed));
$item['title'] = $result->name;
$item['uid'] = $result->id;
$item['uri'] = $this->getTagUrl($result->name);
$item['author'] = $result->last_updater_username;
$item['timestamp'] = $result->tag_last_pushed;
$item['content'] = <<<EOD
<Strong>Tag</strong><br>
<p>{$result->name}</p>
<Strong>Last pushed</strong><br>
<p>{$lastPushed}</p>
<Strong>Images</strong><br>
{$this->getImages($result)}
EOD;
$this->items[] = $item;
}
}
public function getURI() {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/_/' . $this->getRepo();
}
if ($this->getInput('repo')) {
return self::URI . '/r/' . $this->getRepo();
}
return parent::getURI();
}
public function getName() {
if ($this->getInput('repo')) {
return $this->getRepo() . ' - Docker Hub';
}
return parent::getName();
}
private function getRepo() {
if ($this->queriedContext === 'Official Image') {
return $this->getInput('repo');
}
return $this->getInput('user') . '/' . $this->getInput('repo');
}
private function getApiUrl() {
if ($this->queriedContext === 'Official Image') {
return $this->apiURL . 'library/' . $this->getRepo() . '/tags/?page_size=25&page=1';
}
return $this->apiURL . $this->getRepo() . '/tags/?page_size=25&page=1';
}
private function getLayerUrl($name, $digest) {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/layers/' . $this->getRepo() . '/library/' .
$this->getRepo() . '/' . $name . '/images/' . $digest;
}
return self::URI . '/layers/' . $this->getRepo() . '/' . $name . '/images/' . $digest;
}
private function getTagUrl($name) {
if ($this->queriedContext === 'Official Image') {
return self::URI . '/_/' . $this->getRepo() . '?tab=tags&name=' . $name;
}
return self::URI . '/r/' . $this->getRepo() . '/tags?name=' . $name;
}
private function getImages($result) {
$html = <<<EOD
<table style="width:300px;"><thead><tr><th>Digest</th><th>OS/architecture</th></tr></thead></tbody>
EOD;
foreach ($result->images as $image) {
$layersUrl = $this->getLayerUrl($result->name, $image->digest);
$id = $this->getShortDigestId($image->digest);
$html .= <<<EOD
<tr>
<td><a href="{$layersUrl}">{$id}</a></td>
<td>{$image->os}/{$image->architecture}</td>
</tr>
EOD;
}
return $html . '</tbody></table>';
}
private function getShortDigestId($digest) {
$parts = explode(':', $digest);
return substr($parts[1], 0, 12);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -86,7 +86,7 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
private function getImageTag($preview_path, $title){
return sprintf(
'<br /> <a href="%s"><img src="%s" alt="%s" /></a>',
'<br /> <a href="%s"><img srcset="%s" alt="%s" /></a>',
$this->getFullSizeImagePath($preview_path),
$preview_path,
$title
@@ -94,6 +94,11 @@ favicon-63b2904a073c89b52b19aa08cebc16a154bcf83fee8ecc6439968b1e6db569c7.ico';
}
private function getFullSizeImagePath($preview_path){
return explode('?compress=1', $preview_path)[0];
// Get last image from srcset
$src_set_urls = explode(',', $preview_path);
$url = end($src_set_urls);
$url = explode(' ', $url)[1];
return htmlspecialchars_decode($url);
}
}

View File

@@ -14,17 +14,28 @@ class EconomistBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM(self::URI . '/latest/')
or returnServerError('Could not fetch latest updates form The Economist.');
foreach($html->find('article') as $element) {
foreach($html->find('div.teaser') as $element) {
$a = $element->find('a.headline-link', 0);
$href = $a->href;
if (substr($href, 0, 4) != 'http')
$href = self::URI . $a->href;
$a = $element->find('a', 0);
$href = self::URI . $a->href;
$full = getSimpleHTMLDOMCached($href);
$article = $full->find('article', 0);
$header = $article->find('span[itemprop="headline"]', 0);
$headerimg = $article->find('div[itemprop="image"]', 0)->find('img', 0);
$author = $article->find('p[itemprop="byline"]', 0);
$time = $article->find('time', 0);
$content = $article->find('div[itemprop="text"]', 0);
$section = array( $article->find('strong[itemprop="articleSection"]', 0)->plaintext );
$header = $article->find('h1', 0);
$author = $article->find('span[itemprop="author"]', 0);
$time = $article->find('time[itemprop="dateCreated"]', 0);
$content = $article->find('div[itemprop="description"]', 0);
// Author
if ($author)
$author = substr($author->innertext, 3, strlen($author));
else
$author = 'The Economist';
// Remove newsletter subscription box
$newsletter = $content->find('div[class="newsletter-form__message"]', 0);
@@ -40,19 +51,15 @@ class EconomistBridge extends BridgeAbstract {
if ($nextprev)
$nextprev->outertext = '';
$section = array( $article->find('h3[itemprop="articleSection"]', 0)->plaintext );
$item = array();
$item['title'] = $header->find('span', 0)->innertext . ': '
. $header->find('span', 1)->innertext;
$item['title'] = $header->innertext;
$item['uri'] = $href;
$item['timestamp'] = strtotime($time->datetime);
$item['author'] = $author->innertext;
$item['author'] = $author;
$item['categories'] = $section;
$item['content'] = '<img style="max-width: 100%" src="'
. $a->find('img', 0)->src . '">' . $content->innertext;
. $headerimg->src . '">' . $content->innertext;
$this->items[] = $item;

View File

@@ -1,7 +1,7 @@
<?php
class ExtremeDownloadBridge extends BridgeAbstract {
const NAME = 'Extreme Download';
const URI = 'https://www.extreme-down.ninja/';
const URI = 'https://www.extreme-down.tv/';
const DESCRIPTION = 'Suivi de série sur Extreme Download';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
@@ -81,6 +81,16 @@ class ExtremeDownloadBridge extends BridgeAbstract {
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
return self::URI . $this->getInput('url');
break;
default:
return self::URI;
}
}
private function findLinkType($element)
{
$return = '';

View File

@@ -0,0 +1,115 @@
<?php
class FSecureBlogBridge extends BridgeAbstract {
const NAME = 'F-Secure Blog';
const URI = 'https://blog.f-secure.com';
const DESCRIPTION = 'F-Secure Blog';
const MAINTAINER = 'simon816';
const PARAMETERS = array(
'' => array(
'categories' => array(
'name' => 'Blog categories',
'exampleValue' => 'home-security',
),
'language' => array(
'name' => 'Language',
'defaultValue' => 'en',
),
'oldest_date' => array(
'name' => 'Oldest article date',
'exampleValue' => '-2 months',
),
)
);
public function getURI() {
$lang = $this->getInput('language') or 'en';
if ($lang === 'en') {
return self::URI;
}
return self::URI . "/$lang";
}
public function collectData() {
$this->items = array();
$this->seen = array();
$this->oldest = strtotime($this->getInput('oldest_date')) ?: 0;
$categories = $this->getInput('categories');
if (!empty($categories)) {
foreach (explode(',', $categories) as $cat) {
if (!empty($cat)) {
$this->collectCategory($cat);
}
}
return;
}
$html = getSimpleHTMLDOMCached($this->getURI() . '/');
foreach ($html->find('ul.c-header-menu-desktop__list li a') as $link) {
$url = parse_url($link->href);
if (($pos = strpos($url['path'], '/category/')) !== false) {
$cat = substr($url['path'], $pos + strlen('/category/'), -1);
$this->collectCategory($cat);
}
}
}
private function collectCategory($category) {
$url = $this->getURI() . "/category/$category/";
while ($url) {
$url = $this->collectListing($url);
}
}
// n.b. this relies on articles to be ordered by date so the cutoff works
private function collectListing($url) {
$html = getSimpleHTMLDOMCached($url, 60 * 60);
$items = $html->find('section.b-blog .l-blog__content__listing div.c-listing-item');
$catName = trim($html->find('section.b-blog .c-blog-header__title', 0)->plaintext);
foreach ($items as $item) {
$url = $item->getAttribute('data-url');
if (!$this->collectArticle($url)) {
return null; // Too old, stop collecting
}
}
// Point's to 404 for non-english blog
// $next = $html->find('link[rel=next]', 0);
$next = $html->find('ul.page-numbers a.next', 0);
return $next ? $next->href : null;
}
// Returns a boolean whether to continue collecting articles
// i.e. date is after oldest cutoff
private function collectArticle($url) {
if (array_key_exists($url, $this->seen)) {
return true;
}
$html = getSimpleHTMLDOMCached($url);
$rssItem = array( 'uri' => $url, 'uid' => $url );
$rssItem['title'] = $html->find('meta[property=og:title]', 0)->content;
$dt = $html->find('meta[property=article:published_time]', 0)->content;
// Exit if too old
if (strtotime($dt) < $this->oldest) {
return false;
}
$rssItem['timestamp'] = $dt;
$img = $html->find('meta[property=og:image]', 0);
$rssItem['enclosures'] = $img ? array($img->content) : array();
$rssItem['author'] = trim($html->find('.c-blog-author__text a', 0)->plaintext);
$rssItem['categories'] = array_map(function ($link) {
return trim($link->plaintext);
}, $html->find('.b-single-header__categories .c-category-list a'));
$rssItem['content'] = trim($html->find('article', 0)->innertext);
$this->items[] = $rssItem;
$this->seen[$url] = 1;
return true;
}
}

View File

@@ -0,0 +1,104 @@
<?php
class FirefoxAddonsBridge extends BridgeAbstract {
const NAME = 'Firefox Add-ons Bridge';
const URI = 'https://addons.mozilla.org/';
const DESCRIPTION = 'Returns version history for a Firefox Add-on.';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(array(
'id' => array(
'name' => 'Add-on ID',
'type' => 'text',
'required' => true,
'exampleValue' => 'save-to-the-wayback-machine',
)
)
);
const CACHE_TIMEOUT = 3600;
private $feedName = '';
private $releaseDateRegex = '/Released ([\w, ]+) - ([\w. ]+)/';
private $xpiFileRegex = '/([A-Za-z0-9_.-]+)\.xpi$/';
private $outgoingRegex = '/https:\/\/outgoing.prod.mozaws.net\/v1\/(?:[A-z0-9]+)\//';
private $urlRegex = '/addons\.mozilla\.org\/(?:[\w-]+\/)?firefox\/addon\/([\w-]+)/';
public function detectParameters($url) {
$params = array();
if(preg_match($this->urlRegex, $url, $matches)) {
$params['id'] = $matches[1];
return $params;
}
return null;
}
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request: ' . $this->getURI());
$this->feedName = $html->find('h1[class="AddonTitle"] > a', 0)->innertext;
$author = $html->find('span.AddonTitle-author > a', 0)->plaintext;
foreach ($html->find('div.AddonVersionCard-content') as $div) {
$item = array();
$item['title'] = $div->find('h2.AddonVersionCard-version', 0)->plaintext;
$item['uid'] = $item['title'];
$item['uri'] = $this->getURI();
$item['author'] = $author;
if (preg_match($this->releaseDateRegex, $div->find('div.AddonVersionCard-fileInfo', 0)->plaintext, $match)) {
$item['timestamp'] = $match[1];
$size = $match[2];
}
$compatibility = $div->find('div.AddonVersionCard-compatibility', 0)->plaintext;
$license = $div->find('p.AddonVersionCard-license', 0)->innertext;
$downloadlink = $div->find('a.InstallButtonWrapper-download-link', 0)->href;
$releaseNotes = $this->removeOutgoinglink($div->find('div.AddonVersionCard-releaseNotes', 0));
if (preg_match($this->xpiFileRegex, $downloadlink, $match)) {
$xpiFilename = $match[0];
}
$item['content'] = <<<EOD
<strong>Release Notes</strong>
<p>{$releaseNotes}</p>
<strong>Compatibility</strong>
<p>{$compatibility}</p>
<strong>License</strong>
<p>{$license}</p>
<strong>Download</strong>
<p><a href="{$downloadlink}">{$xpiFilename}</a> ($size)</p>
EOD;
$this->items[] = $item;
}
}
public function getURI() {
if (!is_null($this->getInput('id'))) {
return self::URI . 'en-US/firefox/addon/' . $this->getInput('id') . '/versions/';
}
return parent::getURI();
}
public function getName() {
if (!empty($this->feedName)) {
return $this->feedName . ' - Firefox Add-on';
}
return parent::getName();
}
private function removeOutgoinglink($html) {
foreach ($html->find('a') as $a) {
$a->href = urldecode(preg_replace($this->outgoingRegex, '', $a->href));
}
return $html->innertext;
}
}

View File

@@ -20,6 +20,27 @@ class FlickrBridge extends BridgeAbstract {
'required' => true,
'title' => 'Insert keyword',
'exampleValue' => 'bird'
),
'media' => array(
'name' => 'Media',
'type' => 'list',
'values' => array(
'All (Photos & videos)' => 'all',
'Photos' => 'photos',
'Videos' => 'videos',
),
'defaultValue' => 'all',
),
'sort' => array(
'name' => 'Sort By',
'type' => 'list',
'values' => array(
'Relevance' => 'relevance',
'Date uploaded' => 'date-posted-desc',
'Date taken' => 'date-taken-desc',
'Interesting' => 'interestingness-desc',
),
'defaultValue' => 'relevance',
)
),
'By username' => array(
@@ -29,30 +50,60 @@ class FlickrBridge extends BridgeAbstract {
'required' => true,
'title' => 'Insert username (as shown in the address bar)',
'exampleValue' => 'flickr'
),
'media' => array(
'name' => 'Media',
'type' => 'list',
'values' => array(
'All (Photos & videos)' => 'all',
'Photos' => 'photos',
'Videos' => 'videos',
),
'defaultValue' => 'all',
),
'sort' => array(
'name' => 'Sort By',
'type' => 'list',
'values' => array(
'Relevance' => 'relevance',
'Date uploaded' => 'date-posted-desc',
'Date taken' => 'date-taken-desc',
'Interesting' => 'interestingness-desc',
),
'defaultValue' => 'date-posted-desc',
)
)
);
public function collectData(){
private $username = '';
public function collectData() {
switch($this->queriedContext) {
case 'Explore':
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM(self::URI . 'explore')
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request Flickr.');
break;
case 'By keyword':
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM(self::URI . 'search/?q=' . urlencode($this->getInput('q')) . '&s=rec')
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('No results for this query.');
break;
case 'By username':
$filter = 'photo-models';
$html = getSimpleHTMLDOM(self::URI . 'photos/' . urlencode($this->getInput('u')))
//$filter = 'photo-models';
$filter = 'photo-lite-models';
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Requested username can\'t be found.');
$this->username = $this->getInput('u');
if ($html->find('span.search-pill-name', 0)) {
$this->username = $html->find('span.search-pill-name', 0)->plaintext;
}
break;
default:
@@ -64,7 +115,6 @@ class FlickrBridge extends BridgeAbstract {
$photo_models = $this->getPhotoModels($model_json, $filter);
foreach($photo_models as $model) {
$item = array();
/* Author name depends on scope. On a keyword search the
@@ -72,12 +122,12 @@ class FlickrBridge extends BridgeAbstract {
* the author is part of the owner data.
*/
if(array_key_exists('username', $model)) {
$item['author'] = $model['username'];
$item['author'] = urldecode($model['username']);
} elseif (array_key_exists('owner', reset($model_json)[0])) {
$item['author'] = reset($model_json)[0]['owner']['username'];
$item['author'] = urldecode(reset($model_json)[0]['owner']['username']);
}
$item['title'] = (array_key_exists('title', $model) ? $model['title'] : 'Untitled');
$item['title'] = urldecode((array_key_exists('title', $model) ? $model['title'] : 'Untitled'));
$item['uri'] = self::URI . 'photo.gne?id=' . $model['id'];
$description = (array_key_exists('description', $model) ? $model['description'] : '');
@@ -87,7 +137,7 @@ class FlickrBridge extends BridgeAbstract {
. '"><img src="'
. $this->extractContentImage($model)
. '" style="max-width: 640px; max-height: 480px;"/></a><br><p>'
. $description
. urldecode($description)
. '</p>';
$item['enclosures'] = $this->extractEnclosures($model);
@@ -98,6 +148,46 @@ class FlickrBridge extends BridgeAbstract {
}
public function getURI() {
switch($this->queriedContext) {
case 'Explore':
return self::URI . 'explore';
break;
case 'By keyword':
return self::URI . 'search/?q=' . urlencode($this->getInput('q'))
. '&sort=' . $this->getInput('sort') . '&media=' . $this->getInput('media');
break;
case 'By username':
return self::URI . 'search/?user_id=' . urlencode($this->getInput('u'))
. '&sort=' . $this->getInput('sort') . '&media=' . $this->getInput('media');
break;
default:
return parent::getURI();
}
}
public function getName() {
switch($this->queriedContext) {
case 'Explore':
return 'Explore - ' . self::NAME;
break;
case 'By keyword':
return $this->getInput('q') . ' - keyword - ' . self::NAME;
break;
case 'By username':
return $this->username . ' - ' . self::NAME;
break;
default:
return parent::getName();
}
return parent::getName();
}
private function extractJsonModel($html) {
// Find SCRIPT containing JSON data

View File

@@ -0,0 +1,71 @@
<?php
class GenshinImpactBridge extends BridgeAbstract {
const MAINTAINER = 'corenting';
const NAME = 'Genshin Impact';
const URI = 'https://genshin.mihoyo.com/en/news';
const CACHE_TIMEOUT = 7200; // 2h
const DESCRIPTION = 'News from the Genshin Impact website';
const PARAMETERS = array(
array(
'category' => array(
'name' => 'Category',
'type' => 'list',
'values' => array(
'Latest' => 10,
'Info' => 11,
'Updates' => 12,
'Events' => 13
),
'defaultValue' => 10
)
)
);
public function collectData(){
$category = $this->getInput('category');
$url = 'https://genshin.mihoyo.com/content/yuanshen/getContentList';
$url = $url . '?pageSize=3&pageNum=1&channelId=' . $category;
$api_response = getContents($url)
or returnServerError('Error while downloading the website content');
$json_list = json_decode($api_response, true);
foreach($json_list['data']['list'] as $json_item) {
$article_url = 'https://genshin.mihoyo.com/content/yuanshen/getContent';
$article_url = $article_url . '?contentId=' . $json_item['contentId'];
$article_res = getContents($article_url)
or returnServerError('Error while downloading the website content');
$article_json = json_decode($article_res, true);
$article_time = $article_json['data']['start_time'];
$timezone = 'Asia/Shanghai';
$article_timestamp = new DateTime($article_time, new DateTimeZone($timezone));
$item = array();
$item['title'] = $article_json['data']['title'];
$item['timestamp'] = $article_timestamp->format('U');
$item['content'] = $article_json['data']['content'];
$item['uri'] = $this->getArticleUri($json_item);
$item['id'] = $json_item['contentId'];
// Picture
foreach($article_json['data']['ext'] as $ext) {
if ($ext['arrtName'] == 'banner' && count($ext['value']) == 1) {
$item['enclosures'] = array($ext['value'][0]['url']);
break;
}
}
$this->items[] = $item;
}
}
public function getIcon() {
return 'https://genshin.mihoyo.com/favicon.ico';
}
private function getArticleUri($json_item) {
return 'https://genshin.mihoyo.com/en/news/detail/' . $json_item['contentId'];
}
}

View File

@@ -33,17 +33,23 @@ class GithubIssueBridge extends BridgeAbstract {
)
);
// Allows generalization with GithubPullRequestBridge
const BRIDGE_OPTIONS = array(0 => 'Project Issues', 1 => 'Issue comments');
const URL_PATH = 'issues';
const SEARCH_QUERY_PATH = 'issues';
const SEARCH_QUERY = '?q=is%3Aissue+sort%3Aupdated-desc';
public function getName(){
$name = $this->getInput('u') . '/' . $this->getInput('p');
switch($this->queriedContext) {
case 'Project Issues':
case static::BRIDGE_OPTIONS[0]: // Project Issues
$prefix = static::NAME . 's for ';
if($this->getInput('c')) {
$prefix = static::NAME . 's comments for ';
}
$name = $prefix . $name;
break;
case 'Issue comments':
case static::BRIDGE_OPTIONS[1]: // Issue comments
$name = static::NAME . ' ' . $name . ' #' . $this->getInput('i');
break;
default: return parent::getName();
@@ -51,14 +57,14 @@ class GithubIssueBridge extends BridgeAbstract {
return $name;
}
public function getURI(){
public function getURI() {
if(null !== $this->getInput('u') && null !== $this->getInput('p')) {
$uri = static::URI . $this->getInput('u') . '/'
. $this->getInput('p') . '/issues';
if($this->queriedContext === 'Issue comments') {
$uri .= '/' . $this->getInput('i');
} elseif($this->getInput('c')) {
$uri .= '?q=is%3Aissue+sort%3Aupdated-desc';
. $this->getInput('p') . '/';
if($this->queriedContext === static::BRIDGE_OPTIONS[1]) {
$uri .= static::URL_PATH . '/' . $this->getInput('i');
} else {
$uri .= static::SEARCH_QUERY_PATH . static::SEARCH_QUERY;
}
return $uri;
}
@@ -72,19 +78,19 @@ class GithubIssueBridge extends BridgeAbstract {
. $this->getInput('u')
. '/'
. $this->getInput('p')
. '/issues/'
. '/' . static::URL_PATH . '/'
. $issue_number
. '#'
. $comment_id;
}
private function extractIssueEvent($issueNbr, $title, $comment){
private function extractIssueEvent($issueNbr, $title, $comment) {
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
$author = $comment->find('.author', 0);
$author = $comment->find('.author, .avatar', 0);
if ($author) {
$author = $author->plaintext;
$author = trim($author->href, '/');
} else {
$author = '';
}
@@ -95,22 +101,27 @@ class GithubIssueBridge extends BridgeAbstract {
$comment->find('.octicon', 0)->getAttribute('class')
));
$time = $comment->find('relative-time', 0);
if ($time === null) {
return;
}
foreach($comment->find('.Details-content--hidden, .btn') as $el) {
$el->innertext = '';
}
$content = $comment->plaintext;
$item = array();
$item['author'] = $author;
$item['uri'] = $uri;
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
$item['content'] = $content;
return $item;
}
private function extractIssueComment($issueNbr, $title, $comment){
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->parent->id);
private function extractIssueComment($issueNbr, $title, $comment) {
$uri = $this->buildGitHubIssueCommentUri($issueNbr, $comment->id);
$author = $comment->find('.author', 0)->plaintext;
@@ -118,20 +129,23 @@ class GithubIssueBridge extends BridgeAbstract {
$comment->find('.timeline-comment-header-text', 0)->plaintext
);
$time = $comment->find('relative-time', 0);
if ($time === null) {
return;
}
$content = $comment->find('.comment-body', 0)->innertext;
$item = array();
$item['author'] = $author;
$item['uri'] = $uri;
$item['title'] = html_entity_decode($title, ENT_QUOTES, 'UTF-8');
$item['timestamp'] = strtotime(
$comment->find('relative-time', 0)->getAttribute('datetime')
);
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
$item['content'] = $content;
return $item;
}
private function extractIssueComments($issue){
private function extractIssueComments($issue) {
$items = array();
$title = $issue->find('.gh-header-title', 0)->plaintext;
$issueNbr = trim(
@@ -146,41 +160,45 @@ class GithubIssueBridge extends BridgeAbstract {
if ($comment->hasClass('comment')) {
$comment = $comment->parent;
$item = $this->extractIssueComment($issueNbr, $title, $comment);
$items[] = $item;
if ($item !== null) {
$items[] = $item;
}
continue;
} else {
$comment = $comment->parent;
$item = $this->extractIssueEvent($issueNbr, $title, $comment);
$items[] = $item;
if ($item !== null) {
$items[] = $item;
}
}
}
return $items;
}
public function collectData(){
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError(
'No results for Github Issue ' . $this->getURI()
'No results for ' . static::NAME . ' ' . $this->getURI()
);
switch($this->queriedContext) {
case 'Issue comments':
case static::BRIDGE_OPTIONS[1]: // Issue comments
$this->items = $this->extractIssueComments($html);
break;
case 'Project Issues':
case static::BRIDGE_OPTIONS[0]: // Project Issues
foreach($html->find('.js-active-navigation-container .js-navigation-item') as $issue) {
$info = $issue->find('.opened-by', 0);
$issueNbr = substr(
trim($info->plaintext), 1, strpos(trim($info->plaintext), ' ')
);
preg_match('/\/([0-9]+)$/', $issue->find('a', 0)->href, $match);
$issueNbr = $match[1];
$item = array();
$item['content'] = '';
if($this->getInput('c')) {
$uri = static::URI . $this->getInput('u')
. '/' . $this->getInput('p') . '/issues/' . $issueNbr;
. '/' . $this->getInput('p') . '/' . static::URL_PATH . '/' . $issueNbr;
$issue = getSimpleHTMLDOMCached($uri, static::CACHE_TIMEOUT);
if($issue) {
$this->items = array_merge(
@@ -209,7 +227,7 @@ class GithubIssueBridge extends BridgeAbstract {
$item['content'] .= "\n" . 'Comments: ' . $comment_count;
$item['uri'] = self::URI
. $issue->find('.js-navigation-open', 0)->getAttribute('href');
. trim($issue->find('.js-navigation-open', 0)->getAttribute('href'), '/');
$this->items[] = $item;
}
break;
@@ -247,7 +265,7 @@ class GithubIssueBridge extends BridgeAbstract {
$show_comments = 'off';
} break;
case 3: { // Project issues with issue comments
if($path_segments[2] !== 'issues') {
if($path_segments[2] !== static::URL_PATH) {
return null;
}
list($user, $project) = $path_segments;

View File

@@ -0,0 +1,38 @@
<?php
require_once('GithubIssueBridge.php');
class GitHubPullRequestBridge extends GithubIssueBridge {
const MAINTAINER = 'Yaman Qalieh';
const NAME = 'GitHub Pull Request';
const DESCRIPTION = 'Returns the pull request or comments of a pull request of a GitHub project';
const PARAMETERS = array(
'global' => array(
'u' => array(
'name' => 'User name',
'required' => true
),
'p' => array(
'name' => 'Project name',
'required' => true
)
),
'Project Pull Requests' => array(
'c' => array(
'name' => 'Show Pull Request Comments',
'type' => 'checkbox'
)
),
'Pull Request comments' => array(
'i' => array(
'name' => 'Pull Request number',
'type' => 'number',
'required' => true
)
)
);
const BRIDGE_OPTIONS = array(0 => 'Project Pull Requests', 1 => 'Pull Request comments');
const URL_PATH = 'pull';
const SEARCH_QUERY_PATH = 'pulls';
const SEARCH_QUERY = '?q=is%3Apr+sort%3Aupdated-desc';
}

View File

@@ -32,7 +32,7 @@ class HDWallpapersBridge extends BridgeAbstract {
$lastpage = 1;
for($page = 1; $page <= $lastpage; $page++) {
$link = self::URI . '/' . $category . '/page/' . $page;
$link = self::URI . $category . '/page/' . $page;
$html = getSimpleHTMLDOM($link)
or returnServerError('No results for this query.');
@@ -41,13 +41,16 @@ class HDWallpapersBridge extends BridgeAbstract {
$lastpage = min($matches[1], ceil($max / 14));
}
$html = defaultLinkTo($html, self::URI);
foreach($html->find('.wallpapers .wall a') as $element) {
$thumbnail = $element->find('img', 0);
$search = array(self::URI, 'wallpapers.html');
$replace = array(self::URI . 'download/', $this->getInput('r') . '.jpg');
$item = array();
$item['uri'] = self::URI
. '/download'
. str_replace('wallpapers.html', $this->getInput('r') . '.jpg', $element->href);
$item['uri'] = str_replace($search, $replace, $element->href);
$item['timestamp'] = time();
$item['title'] = $element->find('em1', 0)->text();
@@ -55,7 +58,6 @@ class HDWallpapersBridge extends BridgeAbstract {
. '<br><a href="'
. $item['uri']
. '"><img src="'
. self::URI
. $thumbnail->src
. '" /></a>';

View File

@@ -0,0 +1,47 @@
<?php
class HackerNewsUserThreadsBridge extends BridgeAbstract {
const MAINTAINER = 'rakoo';
const NAME = 'Hacker News User Threads';
const URI = 'https://news.ycombinator.com';
const CACHE_TIMEOUT = 7200; // 2 hours
const DESCRIPTION = 'Hacker News threads for a user (at https://news.ycombinator.com/threads?id=xxx)';
const PARAMETERS = array( array(
'user' => array(
'name' => 'User',
'type' => 'text',
'required' => true,
'title' => 'User whose threads you want to see'
)
));
public function collectData(){
$url = 'https://news.ycombinator.com/threads?id=' . $this->getInput('user');
$html = getSimpleHTMLDOM($url) or returnServerError('Could not request HN.');
Debug::log('queried ' . $url);
Debug::log('found ' . $html);
$item = array();
$articles = $html->find('tr[class*="comtr"]');
$story = '';
foreach ($articles as $element) {
$id = $element->getAttribute('id');
$item['uri'] = 'https://news.ycombinator.com/item?id=' . $id;
$author = $element->find('span[class*="comhead"]', 0)->find('a[class="hnuser"]', 0)->innertext;
$newstory = $element->find('span[class*="comhead"]', 0)->find('span[class="storyon"]', 0);
if (count($newstory->find('a')) > 0) {
$story = $newstory->find('a', 0)->innertext;
}
$title = $author . ' | on ' . $story;
$item['author'] = $author;
$item['title'] = $title;
$item['timestamp'] = $element->find('span[class*="age"]', 0)->find('a', 0)->innertext;
$item['content'] = $element->find('span[class*="commtext"]', 0)->innertext;
$this->items[] = $item;
}
}
}

114
bridges/IKWYDBridge.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
class IKWYDBridge extends BridgeAbstract {
const MAINTAINER = 'DevonHess';
const NAME = 'I Know What You Download';
const URI = 'https://iknowwhatyoudownload.com/';
const CACHE_TIMEOUT = 3600; // 1h
const DESCRIPTION = 'Returns torrent downloads and distributions for an IP address';
const PARAMETERS = array(
array(
'ip' => array(
'name' => 'IP Address',
'required' => true
),
'update' => array(
'name' => 'Update last seen',
'type' => 'checkbox',
'title' => 'Update timestamp every time "last seen" changes'
)
)
);
private $name;
private $uri;
public function detectParameters($url) {
$params = array();
$regex = '/^(https?:\/\/)?iknowwhatyoudownload\.com\/';
$regex .= '(?:en|ru)\/peer\/\?ip=(\d+\.\d+\.\d+\.\d+)/';
if(preg_match($regex, $url, $matches) > 0) {
$params['ip'] = urldecode($matches[2]);
return $params;
}
$regex = '/^(https?:\/\/)?iknowwhatyoudownload\.com\/';
$regex .= '(?:(?:en|ru)\/peer\/)?/';
if(preg_match($regex, $url, $matches) > 0) {
$params['ip'] = $_SERVER['REMOTE_ADDR'];
return $params;
}
return null;
}
public function getName() {
if($this->name) {
return $this->name;
} else {
return self::NAME;
}
}
public function getURI() {
if($this->uri) {
return $this->uri;
} else {
return self::URI;
}
}
public function collectData() {
$ip = $this->getInput('ip');
$root = self::URI . 'en/peer/?ip=' . $ip;
$html = getSimpleHTMLDOM($root)
or returnServerError('Could not request ' . self::URI);
$this->name = 'IKWYD: ' . $ip;
$this->uri = $root;
foreach($html->find('.table > tbody > tr') as $download) {
$download = defaultLinkTo($download, self::URI);
$firstSeen = $download->find('.date-column',
0)->innertext;
$lastSeen = $download->find('.date-column',
1)->innertext;
$category = $download->find('.category-column',
0)->innertext;
$torlink = $download->find('.name-column > div > a',
0);
$tortitle = strip_tags($torlink);
$size = $download->find('td', 4)->innertext;
$title = $tortitle;
$author = $ip;
if($this->getInput('update')) {
$timestamp = strtotime($lastSeen);
} else {
$timestamp = strtotime($firstSeen);
}
$uri = $torlink->href;
$content = 'IP address: <a href="' . $root . '">';
$content .= $ip . '</a><br>';
$content .= 'First seen: ' . $firstSeen . '<br>';
$content .= ($this->getInput('update') ? 'Last seen: ' .
$lastSeen . '<br>' : '');
$content .= ($category ? 'Category: ' .
$category . '<br>' : '');
$content .= 'Title: ' . $torlink . '<br>';
$content .= 'Size: ' . $size;
$item = array();
$item['uri'] = $uri;
$item['title'] = $title;
$item['author'] = $author;
$item['timestamp'] = $timestamp;
$item['content'] = $content;
if($category) {
$item['categories'] = array($category);
}
$this->items[] = $item;
}
}
}

View File

@@ -1,7 +1,7 @@
<?php
class InstagramBridge extends BridgeAbstract {
const MAINTAINER = 'pauder';
// const MAINTAINER = 'pauder';
const NAME = 'Instagram Bridge';
const URI = 'https://www.instagram.com/';
const DESCRIPTION = 'Returns the newest images';
@@ -131,7 +131,7 @@ class InstagramBridge extends BridgeAbstract {
switch($media->__typename) {
case 'GraphSidecar':
$data = $this->getInstagramSidecarData($item['uri'], $item['title']);
$data = $this->getInstagramSidecarData($item['uri'], $item['title'], $media, $textContent);
$item['content'] = $data[0];
$item['enclosures'] = $data[1];
break;
@@ -142,7 +142,7 @@ class InstagramBridge extends BridgeAbstract {
$item['enclosures'] = array($mediaURI);
break;
case 'GraphVideo':
$data = $this->getInstagramVideoData($item['uri'], $mediaURI);
$data = $this->getInstagramVideoData($item['uri'], $mediaURI, $media, $textContent);
$item['content'] = $data[0];
if($directLink) {
$item['enclosures'] = $data[1];
@@ -160,11 +160,7 @@ class InstagramBridge extends BridgeAbstract {
}
// returns Sidecar(a post which has multiple media)'s contents and enclosures
protected function getInstagramSidecarData($uri, $postTitle) {
$mediaInfo = $this->getSinglePostData($uri);
$textContent = $this->getTextContent($mediaInfo);
protected function getInstagramSidecarData($uri, $postTitle, $mediaInfo, $textContent) {
$enclosures = array();
$content = '';
foreach($mediaInfo->edge_sidecar_to_children->edges as $singleMedia) {
@@ -187,10 +183,7 @@ class InstagramBridge extends BridgeAbstract {
}
// returns Video post's contents and enclosures
protected function getInstagramVideoData($uri, $mediaURI) {
$mediaInfo = $this->getSinglePostData($uri);
$textContent = $this->getTextContent($mediaInfo);
protected function getInstagramVideoData($uri, $mediaURI, $mediaInfo, $textContent) {
$content = '<video controls>';
$content .= '<source src="' . $mediaInfo->video_url . '" poster="' . $mediaURI . '" type="video/mp4">';
$content .= '<img src="' . $mediaURI . '" alt="">';

View File

@@ -42,7 +42,6 @@ class InternetArchiveBridge extends BridgeAbstract {
$html = defaultLinkTo($html, $this->getURI());
if ($this->getInput('content') !== 'posts') {
$detailsDivNumber = 0;
foreach ($html->find('div.results > div[data-id]') as $index => $result) {
@@ -54,7 +53,6 @@ class InternetArchiveBridge extends BridgeAbstract {
switch($result->class) {
case 'item-ia':
switch($this->getInput('content')) {
case 'reviews':
$item = $this->processReview($result);
@@ -104,7 +102,6 @@ class InternetArchiveBridge extends BridgeAbstract {
public function getName() {
if (!is_null($this->getInput('username')) && !is_null($this->getInput('content'))) {
$contentValues = array_flip(self::PARAMETERS['Account']['content']['values']);
return $contentValues[$this->getInput('content')] . ' - '
@@ -124,11 +121,10 @@ class InternetArchiveBridge extends BridgeAbstract {
}
private function processUpload($result) {
$item = array();
$collection = $result->find('a.stealth', 0);
$collectionLink = self::URI . $collection->href;
$collectionLink = $collection->href;
$collectionTitle = $collection->find('div.item-parent-ttl', 0)->plaintext;
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
@@ -150,7 +146,6 @@ EOD;
}
private function processReview($result) {
$item = array();
$item['title'] = trim($result->find('div.ttl', 0)->innertext);
@@ -172,7 +167,6 @@ EOD;
}
private function processWebArchives($result) {
$item = array();
$item['title'] = trim($result->find('div.ttl', 0)->plaintext);
@@ -189,7 +183,6 @@ EOD;
}
private function processCollection($result) {
$item = array();
$title = trim($result->find('div.collection-title.C.C2', 0)->children(0)->plaintext);
@@ -209,7 +202,6 @@ EOD;
}
private function processHiddenDetails($html, $detailsDivNumber, $item) {
$description = '';
if ($html->find('div.details-ia.hidden-tiles', $detailsDivNumber)) {
@@ -237,7 +229,6 @@ EOD;
}
private function processPosts($html) {
$items = array();
foreach ($html->find('table.forumTable > tr') as $index => $tr) {
@@ -288,6 +279,7 @@ EOD;
break;
}
}
return $items;
}
}

46
bridges/ItchioBridge.php Normal file
View File

@@ -0,0 +1,46 @@
<?php
class ItchioBridge extends BridgeAbstract {
const NAME = 'itch.io';
const URI = 'https://itch.io';
const DESCRIPTION = 'Fetches the file uploads for a product';
const MAINTAINER = 'jacquesh';
const PARAMETERS = array(array(
'url' => array(
'name' => 'Product URL',
'exampleValue' => 'https://remedybg.itch.io/remedybg',
'required' => true,
)
));
const CACHE_TIMEOUT = 21600; // 6 hours
public function collectData() {
$url = $this->getInput('url');
$html = getSimpleHTMLDOM($url)
or returnServerError('Could not request: ' . $url);
$title = $html->find('.game_title', 0)->innertext;
$timestampOriginal = $html->find('span.icon-stopwatch', 0)->parent()->title;
$timestampFormatted = str_replace('@', '', $timestampOriginal);
$content = 'The following files are available to download:<br/>';
foreach ($html->find('div.upload') as $element) {
$filename = $element->find('strong.name', 0)->innertext;
$filesize = $element->find('span.file_size', 0)->first_child()->innertext;
$content = $content . $filename . ' (' . $filesize . ')<br/>';
}
// NOTE: At the time of writing it is not clear under which conditions
// itch updates the timestamp. In case they don't always update it,
// we include the file list as well when computing the UID hash.
$uidContent = $timestampFormatted . $content;
$item = array();
$item['uri'] = $url;
$item['uid'] = $uidContent;
$item['title'] = 'New release for ' . $title;
$item['content'] = $content;
$item['timestamp'] = $timestampFormatted;
$this->items[] = $item;
}
}

View File

@@ -61,6 +61,8 @@ class KernelBugTrackerBridge extends BridgeAbstract {
if($html === false)
returnServerError('Failed to load page!');
$html = defaultLinkTo($html, self::URI);
// Store header information into private members
$this->bugid = $html->find('#bugzilla-body', 0)->find('a', 0)->innertext;
$this->bugdesc = $html->find('table.bugfields', 0)->find('tr', 0)->find('td', 0)->innertext;
@@ -93,7 +95,7 @@ class KernelBugTrackerBridge extends BridgeAbstract {
$item['content'] = str_replace("\n", '<br>', $item['content']);
// Fix relative URIs
$item['content'] = $this->replaceRelativeURI($item['content']);
$item['content'] = $item['content'];
$this->items[] = $item;
}
@@ -125,17 +127,6 @@ class KernelBugTrackerBridge extends BridgeAbstract {
}
}
/**
* Replaces all relative URIs with absolute ones
*
* @param string $content The source string
* @return string Returns the source string with all relative URIs replaced
* by absolute ones.
*/
private function replaceRelativeURI($content){
return preg_replace('/href="(?!http)/', 'href="' . self::URI . '/', $content);
}
/**
* Adds styles as attributes to tags with known classes
*

View File

@@ -352,12 +352,14 @@ class LeBonCoinBridge extends BridgeAbstract {
public function collectData(){
$url = 'https://api.leboncoin.fr/finder/search/';
$url = 'https://api.leboncoin.fr/api/adfinder/v1/search';
$data = $this->buildRequestJson();
$header = array(
'User-Agent: LBC;Android;Null;Null;Null;Null;Null;Null;Null;Null',
'User-Agent: LBC;Android;10;SAMSUNG;phone;0aaaaaaaaaaaaaaa;wifi;8.24.3.8;152437;0',
'Content-Type: application/json',
'X-LBC-CC: 7',
'Accept: application/json,application/hal+json',
'Content-Length: ' . strlen($data),
'api_key: ' . self::$LBC_API_KEY
);

View File

@@ -20,7 +20,7 @@ class MondeDiploBridge extends BridgeAbstract {
$title = $element->find('h3', 0)->plaintext;
$datesAuteurs = $element->find('div.dates_auteurs', 0)->plaintext;
$item = array();
$item['uri'] = self::URI . $element->href;
$item['uri'] = urljoin(self::URI, $element->href);
$item['title'] = $this->cleanText($title) . ' - ' . $this->cleanText($datesAuteurs);
$item['content'] = $this->cleanText(str_replace(array($title, $datesAuteurs), '', $element->plaintext));

View File

@@ -26,7 +26,7 @@ class NordbayernBridge extends BridgeAbstract {
'Gunzenhausen' => 'gunzenhausen',
'Hersbruck' => 'hersbruck',
'Herzogenaurach' => 'herzogenaurach',
'Hilpolstein' => 'holpolstein',
'Hilpoltstein' => 'hilpoltstein',
'Höchstadt' => 'hoechstadt',
'Lauf' => 'lauf',
'Neumarkt' => 'neumarkt',

View File

@@ -1,5 +1,5 @@
<?php
class NyaaTorrentsBridge extends BridgeAbstract {
class NyaaTorrentsBridge extends FeedExpander {
const MAINTAINER = 'ORelio';
const NAME = 'NyaaTorrents';
@@ -50,6 +50,11 @@ class NyaaTorrentsBridge extends BridgeAbstract {
'name' => 'Keyword',
'description' => 'Keyword(s)',
'type' => 'text'
),
'u' => array(
'name' => 'User',
'description' => 'User',
'type' => 'text'
)
)
);
@@ -58,76 +63,45 @@ class NyaaTorrentsBridge extends BridgeAbstract {
return self::URI . 'static/favicon.png';
}
public function collectData() {
public function collectData(){
$this->collectExpandableDatas(
self::URI . '?page=rss&s=id&o=desc&'
. http_build_query(array(
'f' => $this->getInput('f'),
'c' => $this->getInput('c'),
'q' => $this->getInput('q'),
'u' => $this->getInput('u')
)), 20);
}
// Build Search URL from user-provided parameters
$search_url = self::URI . '?s=id&o=desc&'
. http_build_query(array(
'f' => $this->getInput('f'),
'c' => $this->getInput('c'),
'q' => $this->getInput('q')
));
protected function parseItem($newItem){
$item = parent::parseItem($newItem);
// Retrieve torrent listing from search results, which does not contain torrent description
$html = getSimpleHTMLDOM($search_url)
or returnServerError('Could not request Nyaa: ' . $search_url);
$links = $html->find('a');
$results = array();
foreach ($links as $link)
if (strpos($link->href, '/view/') === 0 && !in_array($link->href, $results))
$results[] = $link->href;
if (empty($results) && empty($this->getInput('q')))
returnServerError('No results from Nyaa: ' . $url, 500);
//Convert URI from torrent file to web page
$item['uri'] = str_replace('/download/', '/view/', $item['uri']);
$item['uri'] = str_replace('.torrent', '', $item['uri']);
//Process each item individually
foreach ($results as $element) {
if ($item_html = getSimpleHTMLDOMCached($item['uri'])) {
//Limit total amount of requests
if(count($this->items) >= 20) {
break;
}
//Retrieve full description from page contents
$item_desc = str_get_html(
markdownToHtml(html_entity_decode($item_html->find('#torrent-description', 0)->innertext))
);
$torrent_id = str_replace('/view/', '', $element);
//Ignore entries without valid torrent ID
if ($torrent_id != 0 && ctype_digit($torrent_id)) {
//Retrieve data for this torrent ID
$item_uri = self::URI . 'view/' . $torrent_id;
//Retrieve full description from torrent page
if ($item_html = getSimpleHTMLDOMCached($item_uri)) {
//Retrieve data from page contents
$item_title = str_replace(' :: Nyaa', '', $item_html->find('title', 0)->plaintext);
$item_desc = str_get_html(
markdownToHtml(html_entity_decode($item_html->find('#torrent-description', 0)->innertext))
);
$item_author = extractFromDelimiters($item_html->outertext, 'href="/user/', '"');
$item_date = intval(extractFromDelimiters($item_html->outertext, 'data-timestamp="', '"'));
//Retrieve image for thumbnail or generic logo fallback
$item_image = $this->getURI() . 'static/img/avatar/default.png';
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
}
//Build and add final item
$item = array();
$item['uri'] = $item_uri;
$item['title'] = $item_title;
$item['author'] = $item_author;
$item['timestamp'] = $item_date;
$item['enclosures'] = array($item_image);
$item['content'] = $item_desc;
$this->items[] = $item;
//Retrieve image for thumbnail or generic logo fallback
$item_image = $this->getURI() . 'static/img/avatar/default.png';
foreach ($item_desc->find('img') as $img) {
if (strpos($img->src, 'prez') === false) {
$item_image = $img->src;
break;
}
}
$element = null;
//Add expanded fields to the current item
$item['enclosures'] = array($item_image);
$item['content'] = $item_desc;
}
$results = null;
return $item;
}
}

View File

@@ -123,17 +123,25 @@ class PikabuBridge extends BridgeAbstract {
}
}
$title = $post->find('.story__title-link', 0);
$title_element = $post->find('.story__title-link', 0);
$title = $title_element->plaintext;
$community_link = $post->find('.story__community-link', 0);
// adding special marker for "Maybe News" section
// these posts are fake
if (!is_null($community_link) && $community_link->getAttribute('href') == '/community/maybenews') {
$title = '[' . $community_link->innertext . '] ' . $title;
}
$item = array();
$item['categories'] = $categories;
$item['author'] = $post->find('.user__nick', 0)->innertext;
$item['title'] = $title->plaintext;
$item['title'] = $title;
$item['content'] = strip_tags(
backgroundToImg($post->find('.story__content-inner', 0)->innertext),
'<br><p><img><a>
');
$item['uri'] = $title->href;
$item['uri'] = $title_element->href;
$item['timestamp'] = strtotime($time->getAttribute('datetime'));
$this->items[] = $item;
}

View File

@@ -0,0 +1,45 @@
<?php
class PresidenciaPTBridge extends BridgeAbstract {
const NAME = 'Presidência da República Portuguesa';
const URI = 'https://www.presidencia.pt';
const DESCRIPTION = 'Presidência da República Portuguesa | Mensagens';
const MAINTAINER = 'somini';
const PT_MONTH_NAMES = array(
'janeiro',
'fevereiro',
'março',
'abril',
'maio',
'junho',
'julho',
'agosto',
'setembro',
'outubro',
'novembro',
'dezembro');
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI() . '/atualidade/mensagens')
or returnServerError('Could not load content');
foreach($html->find('#atualidade-list article.card-block') as $element) {
$item = array();
$link = $element->find('a', 0);
$etitle = $link->find('h2', 0);
$edts = $element->find('p', 1);
$edt = html_entity_decode($edts->innertext, ENT_HTML5);
$item['title'] = $etitle->innertext;
$item['uri'] = self::URI . $link->href;
$item['description'] = $element;
$item['timestamp'] = str_ireplace(
array_map(function($name) { return ' de ' . $name . ' de '; }, self::PT_MONTH_NAMES),
array_map(function($num) { return sprintf('-%02d-', $num); }, range(1, sizeof(self::PT_MONTH_NAMES))),
$edt);
$this->items[] = $item;
}
}
}

View File

@@ -0,0 +1,56 @@
<?php
class RaceDepartmentBridge extends FeedExpander {
const NAME = 'RaceDepartment News';
const URI = 'https://racedepartment.com/';
const DESCRIPTION = 'Get the latest (sim)racing news from RaceDepartment.';
const MAINTAINER = 't0stiman';
public function collectData() {
$this->collectExpandableDatas('https://www.racedepartment.com/news/archive.rss', 10);
}
protected function parseItem($feedItem) {
$item = parent::parseItem($feedItem);
//fetch page
$articlePage = getSimpleHTMLDOMCached($feedItem->link)
or returnServerError('Could not retrieve ' . $feedItem->link);
//extract article
$item['content'] = $articlePage->find('div.thfeature_firstPost', 0);
//convert iframes to links. meant for embedded videos.
foreach($item['content']->find('iframe') as $found) {
$iframeUrl = $found->getAttribute('src');
if ($iframeUrl) {
$found->outertext = '<a href="' . $iframeUrl . '">' . $iframeUrl . '</a>';
}
}
//get rid of some elements we don't need
$to_remove_selectors = array(
'div.p-title', //title
'ul.listInline', //Thread starter, Start date
'div.rd_news_article_share_buttons',
'div.thfeature_firstPost-author',
'div.reactionsBar',
'footer',
'div.message-lastEdit',
'section.message-attachments'
);
foreach($to_remove_selectors as $selector) {
foreach($item['content']->find($selector) as $found) {
$found->outertext = '';
}
}
//category
$forumPath = $articlePage->find('div.breadcrumb', 0);
$pathElements = $forumPath->find('span');
$item['categories'] = array(end($pathElements)->innertext);
return $item;
}
}

View File

@@ -25,7 +25,7 @@ class RadioMelodieBridge extends BridgeAbstract {
$picture = array();
// Get the Main picture URL
$picture[] = $this->rewriteImage($article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src);
$picture[] = self::URI . $article->find('div[id=pictureTitleSupport]', 0)->find('img', 0)->src;
$audioHTML = $article->find('audio');
// Add the audio element to the enclosure

View File

@@ -1,12 +1,22 @@
<?php
class RedditBridge extends FeedExpander {
const MAINTAINER = 'leomaradan';
class RedditBridge extends BridgeAbstract {
const MAINTAINER = 'dawidsowa';
const NAME = 'Reddit Bridge';
const URI = 'https://www.reddit.com/';
const DESCRIPTION = 'Reddit RSS Feed fixer';
const URI = 'https://www.reddit.com';
const DESCRIPTION = 'Return hot submissions from Reddit';
const PARAMETERS = array(
'global' => array(
'score' => array(
'name' => 'Minimal score',
'required' => false,
'type' => 'number',
'exampleValue' => 100,
'title' => 'Filter out posts with lower score'
)
),
'single' => array(
'r' => array(
'name' => 'SubReddit',
@@ -22,19 +32,221 @@ class RedditBridge extends FeedExpander {
'exampleValue' => 'selfhosted, php',
'title' => 'SubReddit names, separated by commas'
)
),
'user' => array(
'u' => array(
'name' => 'User',
'required' => true,
'title' => 'User name'
),
'comments' => array(
'type' => 'checkbox',
'name' => 'Comments',
'title' => 'Whether to return comments',
'defaultValue' => false
)
)
);
public function collectData(){
public function detectParameters($url) {
$parsed_url = parse_url($url);
switch($this->queriedContext) {
case 'single': $subreddits[] = $this->getInput('r'); break;
case 'multi': $subreddits = explode(',', $this->getInput('rs')); break;
if ($parsed_url['host'] != 'www.reddit.com' && $parsed_url['host'] != 'old.reddit.com') return null;
$path = explode('/', $parsed_url['path']);
if ($path[1] == 'r') {
return array(
'r' => $path[2]
);
} elseif ($path[1] == 'user') {
return array(
'u' => $path[2]
);
} else {
return null;
}
}
public function getIcon() {
return 'https://www.redditstatic.com/desktop2x/img/favicon/favicon-96x96.png';
}
public function getName() {
if ($this->queriedContext == 'single') {
return 'Reddit r/' . $this->getInput('r');
} elseif ($this->queriedContext == 'user') {
return 'Reddit u/' . $this->getInput('u');
} else {
return self::NAME;
}
}
public function collectData() {
$user = false;
$comments = false;
switch ($this->queriedContext) {
case 'single':
$subreddits[] = $this->getInput('r');
break;
case 'multi':
$subreddits = explode(',', $this->getInput('rs'));
break;
case 'user':
$subreddits[] = $this->getInput('u');
$user = true;
$comments = $this->getInput('comments');
break;
}
foreach ($subreddits as $subreddit) {
$name = trim($subreddit);
$this->collectExpandableDatas("https://www.reddit.com/r/$name/.rss");
$values = getContents(self::URI . ($user ? '/user/' : '/r/') . $name . '.json')
or returnServerError('Unable to fetch posts!');
$decodedValues = json_decode($values);
foreach ($decodedValues->data->children as $post) {
if ($post->kind == 't1' && !$comments) {
continue;
}
$data = $post->data;
if ($data->score < $this->getInput('score')) {
continue;
}
$item = array();
$item['author'] = $data->author;
$item['uid'] = $data->id;
$item['timestamp'] = $data->created_utc;
$item['uri'] = $this->encodePermalink($data->permalink);
$item['categories'] = array();
if ($post->kind == 't1') {
$item['title'] = 'Comment: ' . $data->link_title;
} else {
$item['title'] = $data->title;
$item['categories'][] = $data->link_flair_text;
$item['categories'][] = $data->pinned ? 'Pinned' : null;
$item['categories'][] = $data->spoiler ? 'Spoiler' : null;
}
$item['categories'][] = $data->over_18 ? 'NSFW' : null;
$item['categories'] = array_filter($item['categories']);
if ($post->kind == 't1') {
// Comment
$item['content']
= htmlspecialchars_decode($data->body_html);
} elseif ($data->is_self) {
// Text post
$item['content']
= htmlspecialchars_decode($data->selftext_html);
} elseif (isset($data->post_hint) ? $data->post_hint == 'link' : false) {
// Link with preview
if (isset($data->media)) {
// Reddit embeds content for some sites (e.g. Twitter)
$embed = htmlspecialchars_decode(
$data->media->oembed->html
);
} else {
$embed = '';
}
$item['content'] = $this->template(
$data->url,
$data->thumbnail,
$data->domain
) . $embed;
} elseif (isset($data->post_hint) ? $data->post_hint == 'image' : false) {
// Single image
$item['content'] = $this->link(
$this->encodePermalink($data->permalink),
'<img src="' . $data->url . '" />'
);
} elseif (isset($data->is_gallery) ? $data->is_gallery : false) {
// Multiple images
$images = array();
foreach ($data->gallery_data->items as $media) {
$id = $media->media_id;
$type = $data->media_metadata->$id->m == 'image/gif' ? 'gif' : 'u';
$src = $data->media_metadata->$id->s->$type;
$images[] = '<figure><img src="' . $src . '"/></figure>';
}
$item['content'] = implode('', $images);
} elseif ($data->is_video) {
// Video
// Higher index -> Higher resolution
end($data->preview->images[0]->resolutions);
$index = key($data->preview->images[0]->resolutions);
$item['content'] = $this->template(
$data->url,
$data->preview->images[0]->resolutions[$index]->url,
'Video'
);
} elseif (isset($data->media) ? $data->media->type == 'youtube.com' : false) {
// Youtube link
$item['content'] = $this->template(
$data->url,
$data->media->oembed->thumbnail_url,
'YouTube');
} elseif (explode('.', $data->domain)[0] == 'self') {
// Crossposted text post
// TODO (optionally?) Fetch content of the original post.
$item['content'] = $this->link(
$this->encodePermalink($data->permalink),
'Crossposted from r/'
. explode('.', $data->domain)[1]
);
} else {
// Link WITHOUT preview
$item['content'] = $this->link($data->url, $data->domain);
}
$this->items[] = $item;
}
}
}
private function encodePermalink($link) {
return self::URI . implode(
'/',
array_map('urlencode', explode('/', $link))
);
}
private function template($href, $src, $caption) {
return '<a href="' . $href . '"><figure><figcaption>'
. $caption . '</figcaption><img src="'
. $src . '"/></figure></a>';
}
private function link($href, $text) {
return '<a href="' . $href . '">' . $text . '</a>';
}
}

View File

@@ -8,6 +8,7 @@ class ReporterreBridge extends BridgeAbstract {
private function extractContent($url){
$html2 = getSimpleHTMLDOM($url);
$html2 = defaultLinkTo($html2, self::URI);
foreach($html2->find('div[style=text-align:justify]') as $e) {
$text = $e->outertext;
@@ -16,13 +17,6 @@ class ReporterreBridge extends BridgeAbstract {
$html2->clear();
unset($html2);
// Replace all relative urls with absolute ones
$text = preg_replace(
'/(href|src)(\=[\"\'])(?!http)([^"\']+)/ims',
'$1$2' . self::URI . '$3',
$text
);
$text = strip_tags($text, '<p><br><a><img>');
return $text;
}

261
bridges/ReutersBridge.php Normal file
View File

@@ -0,0 +1,261 @@
<?php
class ReutersBridge extends BridgeAbstract
{
const MAINTAINER = 'hollowleviathan, spraynard, csisoap';
const NAME = 'Reuters Bridge';
const URI = 'https://reuters.com/';
const CACHE_TIMEOUT = 1800; // 30min
const DESCRIPTION = 'Returns news from Reuters';
private $feedName = self::NAME;
/**
* Wireitem types allowed in the final story output
*/
const ALLOWED_WIREITEM_TYPES = array(
'story',
'headlines'
);
/**
* Wireitem template types allowed in the final story output
*/
const ALLOWED_TEMPLATE_TYPES = array(
'story',
'headlines'
);
const PARAMETERS = array(
array(
'feed' => array(
'name' => 'News Feed',
'type' => 'list',
'title' => 'Feeds from Reuters U.S/International edition',
'values' => array(
'Aerospace and Defense' => 'aerospace',
'Business' => 'business',
'China' => 'china',
'Energy' => 'energy',
'Entertainment' => 'chan:8ym8q8dl',
'Environment' => 'chan:6u4f0jgs',
'Health' => 'chan:8hw7807a',
'Lifestyle' => 'life',
'Markets' => 'markets',
'Politics' => 'politics',
'Science' => 'science',
'Special Reports' => 'special-reports',
'Sports' => 'sports',
'Tech' => 'tech',
'Top News' => 'home/topnews',
'UK' => 'chan:61leiu7j',
'USA News' => 'us',
'Wire' => 'wire',
'World' => 'world',
)
)
)
);
/**
* Performs an HTTP request to the Reuters API and returns decoded JSON
* in the form of an associative array
* @param string $feed_uri Parameter string to the Reuters API
* @return array
*/
private function getJson($feed_uri)
{
$uri = "https://wireapi.reuters.com/v8$feed_uri";
$returned_data = getContents($uri);
return json_decode($returned_data, true);
}
/**
* Takes in data from Reuters Wire API and
* creates structured data in the form of a list
* of story information.
* @param array $data JSON collected from the Reuters Wire API
*/
private function processData($data)
{
/**
* Gets a list of wire items which are groups of templates
*/
$reuters_allowed_wireitems = array_filter(
$data, function ($wireitem) {
return in_array(
$wireitem['wireitem_type'],
self::ALLOWED_WIREITEM_TYPES
);
}
);
/*
* Gets a list of "Templates", which is data containing a story
*/
$reuters_wireitem_templates = array_reduce(
$reuters_allowed_wireitems,
function (array $carry, array $wireitem) {
$wireitem_templates = $wireitem['templates'];
return array_merge(
$carry,
array_filter(
$wireitem_templates, function (
array $template_data
) {
return in_array(
$template_data['type'],
self::ALLOWED_TEMPLATE_TYPES
);
}
)
);
},
array()
);
return $reuters_wireitem_templates;
}
private function getArticle($feed_uri)
{
// This will make another request to API to get full detail of article and author's name.
$rawData = $this->getJson($feed_uri);
$reuters_wireitems = $rawData['wireitems'];
$processedData = $this->processData($reuters_wireitems);
$first = reset($processedData);
$article_content = $first['story']['body_items'];
$authorlist = $first['story']['authors'];
$category = $first['story']['channel']['name'];
$image_list = $first['story']['images'];
$content_detail = array(
'content' => $this->handleArticleContent($article_content),
'author' => $this->handleAuthorName($authorlist),
'category' => $category,
'images' => $this->handleImage($image_list),
);
return $content_detail;
}
private function handleImage($images) {
$img_placeholder = '';
foreach($images as $image) { // Add more image to article.
$image_url = $image['url'];
$image_caption = $image['caption'];
$img = "<img src=\"$image_url\">";
$img_caption = "<figcaption style=\"text-align: center;\"><i>$image_caption</i></figcaption>";
$figure = "<figure>$img \t $img_caption</figure>";
$img_placeholder = $img_placeholder . $figure;
}
return $img_placeholder;
}
private function handleAuthorName($authors) {
$author = '';
$counter = 0;
foreach ($authors as $data) {
//Formatting author's name.
$counter++;
$name = $data['name'];
if ($counter == count($authors)) {
$author = $author . $name;
} else {
$author = $author . "$name, ";
}
}
return $author;
}
private function handleArticleContent($contents) {
$description = '';
foreach ($contents as $content) {
$data;
if(isset($content['content'])) {
$data = $content['content'];
}
switch($content['type']) {
case 'paragraph':
$description = $description . "<p>$data</p>";
break;
case 'heading':
$description = $description . "<h3>$data</h3>";
break;
case 'infographics':
$description = $description . "<img src=\"$data\">";
break;
case 'inline_items':
$item_list = $content['items'];
$description = $description . '<p>';
foreach ($item_list as $item) {
if($item['type'] == 'text') {
$description = $description . $item['content'];
} else {
$description = $description . $item['symbol'];
}
}
$description = $description . '</p>';
break;
case 'p_table':
$description = $description . $content['content'];
break;
}
}
return $description;
}
public function getName() {
return $this->feedName;
}
public function collectData()
{
$reuters_feed_name = $this->getInput('feed');
if(strpos($reuters_feed_name, 'chan:') !== false) {
// Now checking whether that feed has unique ID or not.
$feed_uri = "/feed/rapp/us/wirefeed/$reuters_feed_name";
} else {
$feed_uri = "/feed/rapp/us/tabbar/feeds/$reuters_feed_name";
}
$data = $this->getJson($feed_uri);
$reuters_wireitems = $data['wireitems'];
$this->feedName = $data['wire_name'] . ' | Reuters';
$processedData = $this->processData($reuters_wireitems);
// Merge all articles from Editor's Highlight section into existing array of templates.
$top_section = reset($processedData);
if ($top_section['type'] == 'headlines') {
$top_section = array_shift($processedData);
$articles = $top_section['headlines'];
$processedData = array_merge($articles, $processedData);
}
foreach ($processedData as $story) {
$item['uid'] = $story['story']['usn'];
$article_uri = $story['template_action']['api_path'];
$content_detail = $this->getArticle($article_uri);
$description = $content_detail['content'];
$author = $content_detail['author'];
$images = $content_detail['images'];
$item['categories'] = array($content_detail['category']);
$item['author'] = $author;
if (!(bool) $description) {
$description = $story['story']['lede']; // Just in case the content doesn't have anything.
} else {
$item['content'] = "$description $images";
}
$item['title'] = $story['story']['hed'];
$item['timestamp'] = $story['story']['updated_at'];
$item['uri'] = $story['template_action']['url'];
$this->items[] = $item;
}
}
}

View File

@@ -8,12 +8,14 @@ class Rule34pahealBridge extends Shimmie2Bridge {
const URI = 'https://rule34.paheal.net/';
const DESCRIPTION = 'Returns images from given page';
const PATHTODATA = '.shm-thumb';
protected function getItemFromElement($element){
$item = array();
$item['uri'] = $this->getURI() . $element->href;
$item['uri'] = rtrim($this->getURI(), '/') . $element->find('.shm-thumb-link', 0)->href;
$item['id'] = (int)preg_replace('/[^0-9]/', '', $element->getAttribute(static::IDATTRIBUTE));
$item['timestamp'] = time();
$thumbnailUri = $element->find('img', 0)->src;
$thumbnailUri = $element->find('a', 1)->href;
$item['tags'] = $element->getAttribute('data-tags');
$item['title'] = $this->getName() . ' | ' . $item['id'];
$item['content'] = '<a href="'

View File

@@ -0,0 +1,91 @@
<?php
class SeznamZpravyBridge extends BridgeAbstract {
const NAME = 'Seznam Zprávy Bridge';
const URI = 'https://seznamzpravy.cz';
const DESCRIPTION = 'Returns newest stories from Seznam Zprávy';
const MAINTAINER = 'thezeroalpha';
const PARAMETERS = array(
'By Author' => array(
'author' => array(
'name' => 'Author String',
'type' => 'text',
'required' => true,
'title' => 'The dash-separated author string, as shown in the URL bar.',
'pattern' => '[a-z]+-[a-z]+-[0-9]+',
'exampleValue' => 'janek-rubes-506'
),
)
);
private $feedName;
public function getName() {
if (isset($this->feedName)) {
return $this->feedName;
}
return parent::getName();
}
public function collectData() {
$ONE_DAY = 86500;
switch($this->queriedContext) {
case 'By Author':
$url = 'https://www.seznamzpravy.cz/autor/';
$selectors = array(
'breadcrumbs' => 'div[data-dot=ogm-breadcrumb-navigation]',
'article_list' => 'ul.ogm-document-timeline-page.atm-list-ul li article[data-dot=mol-timeline-item]',
'article_title' => 'a[data-dot=mol-article-card-title]',
'article_dm' => 'span.mol-formatted-date__date',
'article_time' => 'span.mol-formatted-date__time',
'article_content' => 'div[data-dot=ogm-article-content]'
);
$html = getSimpleHTMLDOMCached($url . $this->getInput('author'), $ONE_DAY);
$main_breadcrumbs = $html->find($selectors['breadcrumbs'], 0);
$author = $main_breadcrumbs->last_child()->plaintext
or returnServerError('Could not get author on: ' . $this->getURI());
$this->feedName = $author . ' - Seznam Zprávy';
$articles = $html->find($selectors['article_list'])
or returnServerError('Could not find articles on: ' . $this->getURI());
foreach ($articles as $article) {
$title_link = $article->find($selectors['article_title'], 0)
or returnServerError('Could not find title on: ' . $this->getURI());
$article_url = $title_link->href;
$article_content_html = getSimpleHTMLDOMCached($article_url, $ONE_DAY);
$content_e = $article_content_html->find($selectors['article_content'], 0);
$content_text = $content_e->innertext
or returnServerError('Could not get article content for: ' . $article_url);
$breadcrumbs_e = $article_content_html->find($selectors['breadcrumbs'], 0);
$breadcrumbs = $breadcrumbs_e->children();
$num_breadcrumbs = count($breadcrumbs);
$categories = array();
foreach ($breadcrumbs as $cat) {
if (--$num_breadcrumbs <= 0) {
break;
}
$categories[] = trim($cat->plaintext);
}
$article_dm_e = $article->find($selectors['article_dm'], 0);
$article_dm_text = $article_dm_e->plaintext;
$article_dmy = preg_replace('/[^0-9\.]/', '', $article_dm_text) . date('Y');
$article_time = $article->find($selectors['article_time'], 0)->plaintext;
$item = array(
'title' => $title_link->plaintext,
'uri' => $title_link->href,
'timestamp' => strtotime($article_dmy . ' ' . $article_time),
'author' => $author,
'content' => $content_text,
'categories' => $categories
);
$this->items[] = $item;
}
break;
}
$this->items[] = $item;
}
}

View File

@@ -455,6 +455,35 @@ class SkimfeedBridge extends BridgeAbstract {
}
public function detectParameters($url) {
if (0 !== strpos($url, static::URI)) {
return null;
}
foreach(self::PARAMETERS as $channels) {
foreach($channels as $box_name => $box) {
foreach($box['values'] as $name => $channel_url) {
if (static::URI . $channel_url === $url) {
return array(
$box_name => $name,
);
}
}
}
}
return null;
}
public function getName() {
switch($this->queriedContext) {

View File

@@ -27,6 +27,9 @@ class SoundCloudBridge extends BridgeAbstract {
private $feedIcon = null;
private $clientIDCache = null;
private $clientIdRegex = '/client_id.*?"(.+?)"/';
private $widgetRegex = '/widget-.+?\.js/';
public function collectData(){
$res = $this->apiGet('resolve', array(
'url' => 'https://soundcloud.com/' . $this->getInput('u')
@@ -112,21 +115,32 @@ class SoundCloudBridge extends BridgeAbstract {
// Without url=http, this returns a 404
$playerHTML = getContents('https://w.soundcloud.com/player/?url=http')
or returnServerError('Unable to get player page.');
$regex = '/widget-.+?\.js/';
if(preg_match($regex, $playerHTML, $matches) == false)
or returnServerError('Unable to get player page.');
// Extract widget JS filenames from player page
if(preg_match_all($this->widgetRegex, $playerHTML, $matches) == false)
returnServerError('Unable to find widget JS URL.');
$widgetURL = 'https://widget.sndcdn.com/' . $matches[0];
$widgetJS = getContents($widgetURL)
or returnServerError('Unable to get widget JS page.');
$regex = '/client_id.*?"(.+?)"/';
if(preg_match($regex, $widgetJS, $matches) == false)
$clientID = '';
// Loop widget js files and extract client ID
foreach ($matches[0] as $widgetFile) {
$widgetURL = 'https://widget.sndcdn.com/' . $widgetFile;
$widgetJS = getContents($widgetURL)
or returnServerError('Unable to get widget JS page.');
if(preg_match($this->clientIdRegex, $widgetJS, $matches)) {
$clientID = $matches[1];
$this->clientIDCache->saveData($clientID);
return $clientID;
}
}
if (empty($clientID)) {
returnServerError('Unable to find client ID.');
$clientID = $matches[1];
$this->clientIDCache->saveData($clientID);
return $clientID;
}
}
private function buildAPIURL($endpoint, $parameters){

View File

@@ -0,0 +1,34 @@
<?php
class SymfonyCastsBridge extends BridgeAbstract {
const NAME = 'SymfonyCasts Bridge';
const URI = 'https://symfonycasts.com/';
const DESCRIPTION = 'Follow new updates on symfonycasts.com';
const MAINTAINER = 'Park0';
const CACHE_TIMEOUT = 3600;
public function collectData() {
$html = getSimpleHTMLDOM('https://symfonycasts.com/updates/find')
or returnServerError('Unable to get page.');
$dives = $html->find('div');
/* @var simple_html_dom $div */
foreach ($dives as $div) {
$id = $div->getAttribute('data-mark-update-id-value');
$type = $div->find('h5', 0);
$title = $div->find('span', 0);
$dateString = $div->find('h5.font-gray', 0);
$href = $div->find('a', 0);
$url = 'https://symfonycasts.com' . $href->getAttribute('href');
$item = array(); // Create an empty item
$item['uid'] = $id;
$item['title'] = $title->innertext;
$item['timestamp'] = $dateString->innertext;
$item['content'] = $type->plaintext . '<a href="' . $url . '">' . $title . '</a>';
$item['uri'] = $url;
$this->items[] = $item; // Add item to the list
}
}
}

View File

@@ -21,6 +21,18 @@ class TelegramBridge extends BridgeAbstract {
private $itemTitle = '';
private $backgroundImageRegex = "/background-image:url\('(.*)'\)/";
private $detectParamsRegex = '/^https?:\/\/t.me\/(?:s\/)?([\w]+)$/';
public function detectParameters($url) {
$params = array();
if(preg_match($this->detectParamsRegex, $url, $matches) > 0) {
$params['username'] = $matches[1];
return $params;
}
return null;
}
public function collectData() {
@@ -39,8 +51,8 @@ class TelegramBridge extends BridgeAbstract {
$item = array();
$item['uri'] = $this->processUri($messageDiv);
$item['content'] = html_entity_decode($this->processContent($messageDiv), ENT_QUOTES);
$item['title'] = html_entity_decode($this->itemTitle, ENT_QUOTES);
$item['content'] = $this->processContent($messageDiv);
$item['title'] = $this->itemTitle;
$item['timestamp'] = $this->processDate($messageDiv);
$item['enclosures'] = $this->enclosures;
$author = trim($messageDiv->find('a.tgme_widget_message_owner_name', 0)->plaintext);
@@ -120,6 +132,12 @@ class TelegramBridge extends BridgeAbstract {
$messageDiv->find('div.tgme_widget_message_text.js-message_text', 0)->plaintext
);
}
if ($messageDiv->find('div.tgme_widget_message_document', 0)) {
$message .= 'Attachments:';
foreach ($messageDiv->find('div.tgme_widget_message_document') as $attachments) {
$message .= $attachments->find('div.tgme_widget_message_document_title.accent_color', 0);
}
}
if ($messageDiv->find('a.tgme_widget_message_link_preview', 0)) {
$message .= $this->processLinkPreview($messageDiv);

View File

@@ -0,0 +1,51 @@
<?php
class TheFarSideBridge extends BridgeAbstract {
const NAME = 'The Far Side Bridge';
const URI = 'https://www.thefarside.com';
const DESCRIPTION = 'Returns the daily dose';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array();
const CACHE_TIMEOUT = 3600; // 1 hour
public function collectData() {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request: ' . self::URI);
$div = $html->find('div.tfs-page-container__cows', 0);
$item = array();
$item['uri'] = $html->find('meta[property="og:url"]', 0)->content;
$item['title'] = $div->find('h3', 0)->innertext;
$item['timestamp'] = $div->find('h3', 0)->innertext;
$item['content'] = '';
foreach($div->find('div.card-body') as $index => $card) {
$image = $card->find('img', 0);
$imageUrl = $image->attr['data-src'];
// Images are downloaded to bypass the hotlink protection.
$image = getContents($imageUrl, array('Referer: ' . self::URI))
or returnServerError('Could not request: ' . $imageUrl);
// Encode image as base64
$imageBase64 = base64_encode($image);
$caption = '';
if ($card->find('figcaption', 0)) {
$caption = $card->find('figcaption', 0)->innertext;
}
$item['content'] .= <<<EOD
<figure>
<img title="{$caption}" src="data:image/jpeg;base64,{$imageBase64}"/>
<figcaption>{$caption}</figcaption>
</figure>
<br/>
EOD;
}
$this->items[] = $item;
}
}

View File

@@ -12,7 +12,7 @@ class TheYeteeBridge extends BridgeAbstract {
$html = getSimpleHTMLDOM(self::URI)
or returnServerError('Could not request The Yetee.');
$div = $html->find('.hero-col');
$div = $html->find('.module_timed-item.is--full');
foreach($div as $element) {
$item = array();
@@ -21,16 +21,15 @@ class TheYeteeBridge extends BridgeAbstract {
$title = $element->find('h2', 0)->plaintext;
$item['title'] = $title;
$author = trim($element->find('div[class=credit]', 0)->plaintext);
$author = trim($element->find('.module_timed-item--artist a', 0)->plaintext);
$item['author'] = $author;
$uri = $element->find('div[class=controls] a', 0)->href;
$item['uri'] = static::URI . $uri;
$item['uri'] = static::URI;
$content = '<p>' . $element->find('section[class=product-listing-info] p', -1)->plaintext . '</p>';
$photos = $element->find('a[class=js-modaal-gallery] img');
$content = '<p>' . $title . ' by ' . $author . '</p>';
$photos = $element->find('a.img');
foreach($photos as $photo) {
$content = $content . "<br /><img src='$photo->src' />";
$content = $content . "<br /><img src='$photo->href' />";
$item['enclosures'][] = $photo->src;
}
$item['content'] = $content;

155
bridges/TwitScoopBridge.php Normal file
View File

@@ -0,0 +1,155 @@
<?php
class TwitScoopBridge extends BridgeAbstract {
const NAME = 'TwitScoop Bridge';
const URI = 'https://www.twitscoop.com';
const DESCRIPTION = 'Returns trending Twitter topics by country';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array(
array(
'country' => array(
'name' => 'Country',
'type' => 'list',
'values' => array(
'Worldwide' => 'worldwide',
'Algeria' => 'algeria',
'Argentina' => 'argentina',
'Australia' => 'australia',
'Austria' => 'austria',
'Bahrain' => 'bahrain',
'Belarus' => 'belarus',
'Belgium' => 'belgium',
'Brazil' => 'brazil',
'Canada' => 'canada',
'Chile' => 'chile',
'Colombia' => 'colombia',
'Denmark' => 'denmark',
'Dominican Republic' => 'dominican-republic',
'Ecuador' => 'ecuador',
'Egypt' => 'egypt',
'France' => 'france',
'Germany' => 'germany',
'Ghana' => 'ghana',
'Greece' => 'greece',
'Guatemala' => 'guatemala',
'India' => 'india',
'Indonesia' => 'indonesia',
'Ireland' => 'ireland',
'Israel' => 'israel',
'Italy' => 'italy',
'Japan' => 'japan',
'Jordan' => 'jordan',
'Kenya' => 'kenya',
'Korea' => 'korea',
'Kuwait' => 'kuwait',
'Latvia' => 'latvia',
'Lebanon' => 'lebanon',
'Malaysia' => 'malaysia',
'Mexico' => 'mexico',
'Netherlands' => 'netherlands',
'New Zealand' => 'new-zealand',
'Nigeria' => 'nigeria',
'Norway' => 'norway',
'Oman' => 'oman',
'Pakistan' => 'pakistan',
'Panama' => 'panama',
'Peru' => 'peru',
'Philippines' => 'philippines',
'Poland' => 'poland',
'Portugal' => 'portugal',
'Puerto Rico' => 'puerto-rico',
'Qatar' => 'qatar',
'Russia' => 'russia',
'Saudi Arabia' => 'saudi-arabia',
'Singapore' => 'singapore',
'South Africa' => 'south-africa',
'Spain' => 'spain',
'Sweden' => 'sweden',
'Switzerland' => 'switzerland',
'Thailand' => 'thailand',
'Turkey' => 'turkey',
'Ukraine' => 'ukraine',
'United Arab Emirates' => 'united-arab-emirates',
'United Kingdom' => 'united-kingdom',
'United States' => 'united-states',
'Venezuela' => 'venezuela',
'Vietnam' => 'vietnam',
)
),
'limit' => array(
'name' => 'Topics',
'type' => 'number',
'title' => 'Number of trending topics to return. Max 50',
'defaultValue' => 20,
)
)
);
const CACHE_TIMEOUT = 900; // 15 mins
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Could not request: ' . $this->getURI());
$updated = $html->find('time', 0)->datetime;
$trends = $html->find('div.trends', 0);
$limit = $this->getInput('limit');
if ($limit > 50 || $limit < 1) {
$limit = 50;
}
foreach($trends->find('ol.items > li') as $index => $li) {
$number = $index + 1;
$item = array();
$name = rtrim($li->find('span.trend.name', 0)->plaintext, '&nbsp');
$tweets = str_replace(' tweets', '', $li->find('span.tweets', 0)->plaintext);
$tweets = str_replace('<', '', $tweets);
$item['title'] = '#' . $number . ' - ' . $name . ' (' . $tweets . ' tweets)';
$item['uri'] = 'https://twitter.com/search?q=' . rawurlencode($name);
if ($tweets === '10K') {
$tweets = 'less than 10K';
}
$item['content'] = <<<EOD
<strong>Rank</strong><br>
<p>{$number}</p>
<Strong>Topic</strong><br>
<p>{$name}</p>
<Strong>Tweets</strong><br>
<p>{$tweets}</p>
EOD;
$item['timestamp'] = $updated;
$this->items[] = $item;
if (count($this->items) >= $limit) {
break;
}
}
}
public function getURI() {
if (!is_null($this->getInput('country'))) {
return self::URI . '/' . $this->getInput('country');
}
return parent::getURI();
}
public function getName() {
if (!is_null($this->getInput('country'))) {
$parameters = $this->getParameters();
$values = array_flip($parameters[0]['country']['values']);
return $values[$this->getInput('country')] . ' - TwitScoop';
}
return parent::getName();
}
}

View File

@@ -75,6 +75,12 @@ EOD
'required' => false,
'type' => 'checkbox',
'title' => 'Hide retweets'
),
'nopinned' => array(
'name' => 'Without pinned tweet',
'required' => false,
'type' => 'checkbox',
'title' => 'Hide pinned tweet'
)
),
'By list' => array(
@@ -199,10 +205,22 @@ EOD
. urlencode($this->getInput('q'))
. '&tweet_mode=extended&tweet_search_mode=live';
case 'By username':
return self::API_URI
. '/2/timeline/profile/'
. $this->getRestId($this->getInput('u'))
. '.json?tweet_mode=extended';
// use search endpoint if without replies or without retweets enabled
if ($this->getInput('noretweet') || $this->getInput('norep')) {
$query = 'from:' . $this->getInput('u');
// Twitter's from: search excludes retweets by default
if (!$this->getInput('noretweet')) $query .= ' include:nativeretweets';
if ($this->getInput('norep')) $query .= ' exclude:replies';
return self::API_URI
. '/2/search/adaptive.json?q='
. urlencode($query)
. '&tweet_mode=extended&tweet_search_mode=live';
} else {
return self::API_URI
. '/2/timeline/profile/'
. $this->getRestId($this->getInput('u'))
. '.json?tweet_mode=extended';
}
case 'By list':
return self::API_URI
. '/2/timeline/list.json?list_id='
@@ -246,7 +264,43 @@ EOD
return $carry;
}, array());
foreach($data->globalObjects->tweets as $tweet) {
$hidePinned = $this->getInput('nopinned');
if ($hidePinned) {
$pinnedTweetId = null;
if (isset($data->timeline->instructions[1]) && isset($data->timeline->instructions[1]->pinEntry)) {
$pinnedTweetId = $data->timeline->instructions[1]->pinEntry->entry->content->item->content->tweet->id;
}
}
$tweets = array();
// Extract tweets from timeline property when in username mode
// This fixes number of issues:
// * If there's a retweet of a quote tweet, the quoted tweet will not appear in results (since it wasn't retweeted directly)
// * Pinned tweets do not get stuck at the bottom
if ($this->queriedContext === 'By username') {
foreach($data->timeline->instructions[0]->addEntries->entries as $tweet) {
if (!isset($tweet->content->item)) continue;
$tweetId = $tweet->content->item->content->tweet->id;
$selectedTweet = $this->getTweet($tweetId, $data->globalObjects);
if (!$selectedTweet) continue;
// If this is a retweet, it will contain shorter text and will point to the original full tweet (retweeted_status_id_str).
// Let's use the original tweet text.
if (isset($selectedTweet->retweeted_status_id_str)) {
$tweetId = $selectedTweet->retweeted_status_id_str;
$selectedTweet = $this->getTweet($tweetId, $data->globalObjects);
if (!$selectedTweet) continue;
}
// use $tweetId as key to avoid duplicates (e.g. user retweeting their own tweet)
$tweets[$tweetId] = $selectedTweet;
}
} else {
foreach($data->globalObjects->tweets as $tweet) {
$tweets[] = $tweet;
}
}
foreach($tweets as $tweet) {
/* Debug::log('>>> ' . json_encode($tweet)); */
// Skip spurious retweets
@@ -259,6 +313,11 @@ EOD
continue;
}
// Skip pinned tweet
if ($hidePinned && $tweet->id_str === $pinnedTweetId) {
continue;
}
$item = array();
// extract username and sanitize
$user_info = $this->getUserInformation($tweet->user_id_str, $data->globalObjects);
@@ -266,7 +325,7 @@ EOD
$item['username'] = $user_info->screen_name;
$item['fullname'] = $user_info->name;
$item['author'] = $item['fullname'] . ' (@' . $item['username'] . ')';
if (null !== $this->getInput('u') && $item['username'] != $this->getInput('u')) {
if (null !== $this->getInput('u') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
$item['author'] .= ' RT: @' . $this->getInput('u');
}
$item['avatar'] = $user_info->profile_image_url_https;
@@ -388,7 +447,7 @@ EOD;
}
break;
case 'By username':
if ($this->getInput('noretweet') && $item['username'] != $this->getInput('u')) {
if ($this->getInput('noretweet') && strtolower($item['username']) != strtolower($this->getInput('u'))) {
continue 2; // switch + for-loop!
}
break;
@@ -548,4 +607,12 @@ EOD;
}
}
}
private function getTweet($tweetId, $apiData) {
if (property_exists($apiData->tweets, $tweetId)) {
return $apiData->tweets->$tweetId;
} else {
return null;
}
}
}

View File

@@ -232,11 +232,16 @@ class VkBridge extends BridgeAbstract
$div->outertext = '';
}
// get sign
// get sign / post author
$post_author = $pageName;
foreach($post->find('a.wall_signed_by') as $a) {
$post_author = $a->innertext;
$a->outertext = '';
$author_selectors = array('a.wall_signed_by', 'a.author');
foreach($author_selectors as $author_selector) {
$a = $post->find($author_selector, 0);
if (is_object($a)) {
$post_author = $a->innertext;
$a->outertext = '';
break;
}
}
// fix links and get post hashtags
@@ -274,16 +279,24 @@ class VkBridge extends BridgeAbstract
}
}
if (is_object($post->find('div.copy_quote', 0))) {
$copy_quote = $post->find('div.copy_quote', 0);
if (is_object($copy_quote)) {
if ($this->getInput('hide_reposts') === true) {
continue;
}
$copy_quote = $post->find('div.copy_quote', 0);
if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) {
$copy_post_header->outertext = '';
}
$second_copy_quote = $copy_quote->find('div.published_sec_quote', 0);
if (is_object($second_copy_quote)) {
$second_copy_quote_author = $second_copy_quote->find('a.copy_author', 0)->outertext;
$second_copy_quote_content = $second_copy_quote->find('div.copy_post_date', 0)->outertext;
$second_copy_quote->outertext = "<br>Reposted ($second_copy_quote_author): $second_copy_quote_content";
}
$copy_quote_author = $copy_quote->find('a.copy_author', 0)->outertext;
$copy_quote_content = $copy_quote->innertext;
$copy_quote->outertext = "<br>Reposted: <br>$copy_quote_content";
$copy_quote->outertext = "<br>Reposted ($copy_quote_author): <br>$copy_quote_content";
}
$item = array();
@@ -333,7 +346,7 @@ class VkBridge extends BridgeAbstract
$data = json_decode($arg, true);
if ($data == null) return;
$thumb = $data['temp']['base'] . $data['temp']['x_'][0] . '.jpg';
$thumb = $data['temp']['base'] . $data['temp']['x_'][0];
$original = '';
foreach(array('y_', 'z_', 'w_') as $key) {
if (!isset($data['temp'][$key])) continue;
@@ -343,7 +356,7 @@ class VkBridge extends BridgeAbstract
} else {
$base = $data['temp']['base'];
}
$original = $base . $data['temp'][$key][0] . '.jpg';
$original = $base . $data['temp'][$key][0];
}
if ($original) {
@@ -366,6 +379,7 @@ class VkBridge extends BridgeAbstract
return $time;
} else {
$strdate = $post->find('span.rel_date', 0)->plaintext;
$strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate);
$date = date_parse($strdate);
if (!$date['year']) {

View File

@@ -0,0 +1,50 @@
<?php
class WallmineNewsBridge extends BridgeAbstract {
const NAME = 'Wallmine News Bridge';
const URI = 'https://wallmine.com';
const DESCRIPTION = 'Returns financial news';
const MAINTAINER = 'VerifiedJoseph';
const PARAMETERS = array();
const CACHE_TIMEOUT = 900; // 15 mins
public function collectData() {
$html = getSimpleHTMLDOM($this->getURI() . '/news/')
or returnServerError('Could not request: ' . $this->getURI() . '/news/');
$html = defaultLinkTo($html, self::URI);
foreach($html->find('div.container.news-card') as $div) {
$item = array();
$item['uri'] = $div->find('a', 0)->href;
$image = $div->find('img.img-fluid', 0)->src;
$page = getSimpleHTMLDOMCached($item['uri'], 7200)
or returnServerError('Could not request: ' . $item['uri']);
$article = $page->find('div.container.article-container', 0);
$item['title'] = $article->find('h1', 0)->plaintext;
$article->find('p.published-on', 0)->children(0)->outertext = '';
$article->find('p.published-on', 0)->children(1)->outertext = '';
$date = str_replace('at', '', $article->find('p.published-on', 0)->innertext);
$item['timestamp'] = $date;
$article->find('h1', 0)->outertext = '';
$article->find('p.published-on', 0)->outertext = '';
$item['content'] = $article->innertext;
$item['enclosures'][] = $image;
$this->items[] = $item;
if (count($this->items) >= 10) {
break;
}
}
}
}

93
bridges/YeggiBridge.php Normal file
View File

@@ -0,0 +1,93 @@
<?php
class YeggiBridge extends BridgeAbstract {
const NAME = 'Yeggi Search';
const URI = 'https://www.yeggi.com';
const DESCRIPTION = 'Returns feeds for search results';
const MAINTAINER = 'AntoineTurmel';
const PARAMETERS = array(
array(
'query' => array(
'name' => 'Search query',
'type' => 'text',
'required' => true,
'title' => 'Insert your search term here',
'exampleValue' => 'Enter your search term'
),
'sortby' => array(
'name' => 'Sort by',
'type' => 'list',
'required' => false,
'values' => array(
'Best match' => '0',
'Popular' => '1',
'Latest' => '2',
),
'defaultValue' => 'newest'
),
'show' => array(
'name' => 'Show',
'type' => 'list',
'required' => false,
'values' => array(
'All' => '0',
'Free' => '1',
'For sale' => '2',
),
'defaultValue' => 'all'
),
'showimage' => array(
'name' => 'Show image in content',
'type' => 'checkbox',
'required' => false,
'title' => 'Activate to show the image in the content',
'defaultValue' => 'checked'
)
)
);
public function collectData(){
$html = getSimpleHTMLDOM($this->getURI())
or returnServerError('Failed to receive ' . $this->getURI());
$results = $html->find('div.item_1_A');
foreach($results as $result) {
$item = array();
$title = $result->find('.item_3_B_2', 0)->plaintext;
$explodeTitle = explode('&nbsp; ', $title);
if(count($explodeTitle) == 2) {
$item['title'] = $explodeTitle[1];
} else {
$item['title'] = $explodeTitle[0];
}
$item['uri'] = self::URI . $result->find('a', 0)->href;
$item['author'] = 'Yeggi';
$item['content'] = '';
$item['uid'] = hash('md5', $item['title']);
$image = $result->find('img', 0)->src;
if($this->getInput('showimage')) {
$item['content'] .= '<img src="' . $image . '">';
}
$item['enclosures'] = array($image);
$this->items[] = $item;
}
}
public function getURI(){
if(!is_null($this->getInput('query'))) {
$uri = self::URI . '/q/' . urlencode($this->getInput('query')) . '/';
$uri .= '?o_f=' . $this->getInput('show');
$uri .= '&o_s=' . $this->getInput('sortby');
return $uri;
}
return parent::getURI();
}
}

View File

@@ -8,7 +8,7 @@ class ZoneTelechargementBridge extends BridgeAbstract {
*/
const NAME = 'Zone Telechargement';
const URI = 'https://www.zt-za.com/';
const URI = 'https://www.zt-za.net/';
const DESCRIPTION = 'Suivi de série sur Zone Telechargement';
const MAINTAINER = 'sysadminstory';
const PARAMETERS = array(
@@ -18,21 +18,45 @@ class ZoneTelechargementBridge extends BridgeAbstract {
'type' => 'text',
'required' => true,
'title' => 'URL d\'une série sans le https://www.zt-za.com/',
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'
'exampleValue' => 'telecharger-series/31079-halt-and-catch-fire-saison-4-french-hd720p.html'),
'filter' => array(
'name' => 'Type de contenu',
'type' => 'list',
'title' => 'Type de contenu à suivre : Téléchargement, Streaming ou les deux',
'values' => array(
'Streaming et Téléchargement' => 'both',
'Téléchargement' => 'download',
'Streaming' => 'streaming'
),
'defaultValue' => 'both'
)
)
);
// This is an URL that is not protected by robot protection
const UNPROTECED_URI = 'https://www.zone-annuaire.com/';
// This is an URL that is not protected by robot protection for Direct Download
const UNPROTECTED_URI = 'https://www.zone-telechargement.net/';
public function getIcon() {
return self::URI . '/templates/Default/images/favicon.ico';
// This is an URL that is not protected by robot protection for Streaming Links
const UNPROTECTED_URI_STREAMING = 'https://zone-telechargement.stream/';
// This function use curl library with curl as User Agent instead of
// simple_html_dom to load the HTML content as the website has some captcha
// request for other user agents
private function loadURL($url){
$header = array();
$opts = array(CURLOPT_USERAGENT => 'curl/7.64.0');
$html = getContents($url, $header, $opts)
or returnServerError('Could not request Zone Telechargement.');
return str_get_html($html);
}
public function getIcon(){
return self::UNPROTECTED_URI . '/templates/Default/images/favicon.ico';
}
public function collectData(){
$html = getSimpleHTMLDOM(self::UNPROTECED_URI . $this->getInput('url'))
or returnServerError('Could not request Zone Telechargement.');
$html = $this->loadURL(self::UNPROTECTED_URI . $this->getInput('url'));
$filter = $this->getInput('filter');
// Get the TV show title
$qualityselector = 'div[style=font-size: 18px;margin: 10px auto;color:red;font-weight:bold;text-align:center;]';
@@ -40,35 +64,71 @@ class ZoneTelechargementBridge extends BridgeAbstract {
$quality = trim(explode("\n", $html->find($qualityselector, 0)->plaintext)[0]);
$this->showTitle = $show . ' ' . $quality;
// Get the post content
$linkshtml = $html->find('div[class=postinfo]', 0);
$episodes = array();
$list = $linkshtml->find('a');
// Construct the tabble of episodes using the links
foreach($list as $element) {
// Retrieve episode number from link text
$epnumber = explode(' ', $element->plaintext)[1];
$hoster = $this->findLinkHoster($element);
// Handle the Direct Download links
if($filter == 'both' || $filter == 'download') {
// Get the post content
$linkshtml = $html->find('div[class=postinfo]', 0);
// Format the link and add the link to the corresponding episode table
$episodes[$epnumber][] = '<a href="' . $element->href . '">' . $hoster . ' - '
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
$list = $linkshtml->find('a');
// Construct the table of episodes using the links
foreach($list as $element) {
// Retrieve episode number from link text
$epnumber = explode(' ', $element->plaintext)[1];
$hoster = $this->findLinkHoster($element);
// Format the link and add the link to the corresponding episode table
$episodes[$epnumber]['ddl'][] = '<a href="' . $element->href . '">' . $hoster . ' - '
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
}
}
// Handle the Streaming links
if($filter == 'both' || $filter == 'streaming') {
// Get the post content, on the dedicated streaming website
$htmlstreaming = $this->loadURL(self::UNPROTECTED_URI_STREAMING . $this->getInput('url'));
// Get the HTML element containing all the links
$streaminglinkshtml = $htmlstreaming->find('p[style=background-color: #FECC00;]', 1)->parent()->next_sibling();
// Get all streaming Links
$liststreaming = $streaminglinkshtml->find('a');
foreach($liststreaming as $elementstreaming) {
// Retrieve the episode number from the link text
$epnumber = explode(' ', $elementstreaming->plaintext)[1];
// Format the link and add the link to the corresponding episode table
$episodes[$epnumber]['streaming'][] = '<a href="' . $elementstreaming->href . '">'
. $this->showTitle . ' Episode ' . $epnumber . '</a>';
}
}
// Finally construct the items array
foreach($episodes as $epnum => $episode) {
$item = array();
// Add every link available in the episode table separated by a <br/> tag
$item['content'] = implode('<br/>', $episode);
$item['title'] = $this->showTitle . ' Episode ' . $epnum;
// As RSS Bridge use the URI as GUID they need to be unique : adding a md5 hash of the title element
// should geneerate unique URI to prevent confusion for RSS readers
$item['uri'] = self::URI . $this->getInput('url') . '#' . hash('md5', $item['title']);
// Insert the episode at the beginning of the item list, to show the newest episode first
array_unshift($this->items, $item);
// Handle the Direct Download links
if(array_key_exists('ddl', $episode)) {
$item = array();
// Add every link available in the episode table separated by a <br/> tag
$item['content'] = implode('<br/>', $episode['ddl']);
$item['title'] = $this->showTitle . ' Episode ' . $epnum . ' - Téléchargement';
// Generate an unique UID by hashing the item title to prevent confusion for RSS readers
$item['uid'] = hash('md5', $item['title']);
$item['uri'] = self::URI . $this->getInput('url');
// Insert the episode at the beginning of the item list, to show the newest episode first
array_unshift($this->items, $item);
}
// Handle the streaming link
if(array_key_exists('streaming', $episode)) {
$item = array();
// Add every link available in the episode table separated by a <br/> tag
$item['content'] = implode('<br/>', $episode['streaming']);
$item['title'] = $this->showTitle . ' Episode ' . $epnum . ' - Streaming';
// Generate an unique UID by hashing the item title to prevent confusion for RSS readers
$item['uid'] = hash('md5', $item['title']);
$item['uri'] = self::URI . $this->getInput('url');
// Insert the episode at the beginning of the item list, to show the newest episode first
array_unshift($this->items, $item);
}
}
}
@@ -82,11 +142,19 @@ class ZoneTelechargementBridge extends BridgeAbstract {
}
}
public function getURI() {
switch($this->queriedContext) {
case 'Suivre la publication des épisodes d\'une série en cours de diffusion':
return self::URI . $this->getInput('url');
break;
default:
return self::URI;
}
}
private function findLinkHoster($element) {
// The hoster name is one level higher than the link tag : get the parent element
$element = $element->parent();
//echo "PARENT : $element \n";
$continue = true;
// Walk through all elements in the reverse order until finding the one with a div and that is not a <br/>
while(!($element->find('div', 0) != null && $element->tag != 'br')) {
$element = $element->prev_sibling();

View File

@@ -19,7 +19,7 @@ class HtmlFormat extends FormatAbstract {
continue;
}
$query = str_replace('format=Html', 'format=' . $format, htmlentities($_SERVER['QUERY_STRING']));
$query = str_ireplace('format=Html', 'format=' . $format, htmlentities($_SERVER['QUERY_STRING']));
$buttons .= $this->buildButton($format, $query) . PHP_EOL;
$mime = $formatFac->create($format)->getMimeType();

View File

@@ -61,6 +61,13 @@ abstract class BridgeAbstract implements BridgeInterface {
*/
const CACHE_TIMEOUT = 3600;
/**
* Configuration for the bridge
*
* Use {@see BridgeAbstract::getConfiguration()} to read this parameter
*/
const CONFIGURATION = array();
/**
* Parameters for the bridge
*
@@ -238,6 +245,36 @@ abstract class BridgeAbstract implements BridgeInterface {
}
/**
* Loads configuration for the bridge
*
* Returns errors and aborts execution if the provided configuration is
* invalid.
*
* @return void
*/
public function loadConfiguration() {
foreach(static::CONFIGURATION as $optionName => $optionValue) {
$configurationOption = Configuration::getConfig(get_class($this), $optionName);
if($configurationOption !== null) {
$this->configuration[$optionName] = $configurationOption;
continue;
}
if(isset($optionValue['required']) && $optionValue['required'] === true) {
returnServerError(
'Missing configuration option: '
. $optionName
);
} elseif(isset($optionValue['defaultValue'])) {
$this->configuration[$optionName] = $optionValue['defaultValue'];
}
}
}
/**
* Returns the value for the provided input
*
@@ -251,6 +288,19 @@ abstract class BridgeAbstract implements BridgeInterface {
return $this->inputs[$this->queriedContext][$input]['value'];
}
/**
* Returns the value for the selected configuration
*
* @param string $input The option name
* @return mixed|null The option value or null if the input is not defined
*/
public function getOption($name){
if(!isset($this->configuration[$name])) {
return null;
}
return $this->configuration[$name];
}
/** {@inheritdoc} */
public function getDescription(){
return static::DESCRIPTION;
@@ -268,7 +318,12 @@ abstract class BridgeAbstract implements BridgeInterface {
/** {@inheritdoc} */
public function getIcon(){
return '';
return static::URI . '/favicon.ico';
}
/** {@inheritdoc} */
public function getConfiguration(){
return static::CONFIGURATION;
}
/** {@inheritdoc} */

View File

@@ -347,11 +347,13 @@ This bridge is not fetching its content through a secure connection</div>';
CARD;
// If we don't have any parameter for the bridge, we print a generic form to load it.
if(count($parameters) === 0
|| count($parameters) === 1 && array_key_exists('global', $parameters)) {
if (count($parameters) === 0) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps);
// Display form with cache timeout and/or noproxy options (if enabled) when bridge has no parameters
} else if (count($parameters) === 1 && array_key_exists('global', $parameters)) {
$card .= self::getForm($bridgeName, $formats, $isActive, $isHttps, '', $parameters['global']);
} else {
foreach($parameters as $parameterName => $parameter) {

View File

@@ -58,6 +58,19 @@ interface BridgeInterface {
*/
public function collectData();
/**
* Get the user's supplied configuration for the bridge
*/
public function getConfiguration();
/**
* Returns the value for the selected configuration
*
* @param string $input The option name
* @return mixed|null The option value or null if the input is not defined
*/
public function getOption($name);
/**
* Returns the description
*

View File

@@ -28,7 +28,7 @@ final class Configuration {
*
* @todo Replace this property by a constant.
*/
public static $VERSION = 'dev.2020-11-10';
public static $VERSION = 'dev.2021-04-25';
/**
* Holds the configuration data.
@@ -145,10 +145,7 @@ final class Configuration {
// Replace default configuration with custom settings
foreach(parse_ini_file(FILE_CONFIG, true, INI_SCANNER_TYPED) as $header => $section) {
foreach($section as $key => $value) {
// Skip unknown sections and keys
if(array_key_exists($header, Configuration::$config) && array_key_exists($key, Configuration::$config[$header])) {
Configuration::$config[$header][$key] = $value;
}
Configuration::$config[$header][$key] = $value;
}
}
}
@@ -218,13 +215,11 @@ final class Configuration {
* @return mixed|null The parameter value.
*/
public static function getConfig($section, $key) {
if(array_key_exists($section, self::$config) && array_key_exists($key, self::$config[$section])) {
return self::$config[$section][$key];
}
return null;
}
/**

View File

@@ -270,7 +270,9 @@ abstract class FeedExpander extends BridgeAbstract {
foreach($feedItem->link as $link) {
if(strtolower($link['rel']) === 'alternate') {
$item['uri'] = (string)$link['href'];
break;
}
if(strtolower($link['rel']) === 'enclosure') {
$item['enclosures'][] = (string)$link['href'];
}
}
}

View File

@@ -46,7 +46,13 @@ class FormatFactory extends FactoryAbstract {
throw new \InvalidArgumentException('Format name invalid!');
}
$name = $this->sanitizeFormatName($name) . 'Format';
$name = $this->sanitizeFormatName($name);
if (is_null($name)) {
throw new \InvalidArgumentException('Unknown format given!');
}
$name .= 'Format';
$pathFormat = $this->getWorkingDir() . $name . '.php';
if(!file_exists($pathFormat)) {
@@ -72,7 +78,7 @@ class FormatFactory extends FactoryAbstract {
* @return bool true if the name is a valid format name, false otherwise.
*/
public function isFormatName($name){
return is_string($name) && preg_match('/^[A-Z][a-zA-Z0-9-]*$/', $name) === 1;
return is_string($name) && preg_match('/^[a-zA-Z0-9-]*$/', $name) === 1;
}
/**
@@ -108,8 +114,6 @@ class FormatFactory extends FactoryAbstract {
* * The PHP file name without file extension (i.e. `AtomFormat`)
* * The format name (i.e. `Atom`)
*
* Casing is ignored (i.e. `ATOM` and `atom` are the same).
*
* A format file matching the given format name must exist in the working
* directory!
*
@@ -118,6 +122,7 @@ class FormatFactory extends FactoryAbstract {
* valid, null otherwise.
*/
protected function sanitizeFormatName($name) {
$name = ucfirst(strtolower($name));
if(is_string($name)) {
@@ -131,18 +136,12 @@ class FormatFactory extends FactoryAbstract {
$name = $matches[1];
}
// Improve performance for correctly written format names
// The name is valid if a corresponding format file is found on disk
if(in_array($name, $this->getFormatNames())) {
$index = array_search($name, $this->getFormatNames());
return $this->getFormatNames()[$index];
}
// The name is valid if a corresponding format file is found on disk
if(in_array(strtolower($name), array_map('strtolower', $this->getFormatNames()))) {
$index = array_search(strtolower($name), array_map('strtolower', $this->getFormatNames()));
return $this->getFormatNames()[$index];
}
Debug::log('Invalid format name: "' . $name . '"!');
}

View File

@@ -198,8 +198,7 @@ EOD
if($lastError !== null)
$lastError = $lastError['message'];
returnError(<<<EOD
The requested resource cannot be found!
Please make sure your input parameters are correct!
Unexpected response from upstream.
cUrl error: $curlError ($curlErrno)
PHP error: $lastError
EOD
@@ -312,7 +311,7 @@ function getSimpleHTMLDOMCached($url,
$time = $cache->getTime();
if($time !== false
&& (time() - $duration < $time)
&& Debug::isEnabled()) { // Contents within duration
&& !Debug::isEnabled()) { // Contents within duration
$content = $cache->loadData();
} else { // Content not within duration
$content = getContents($url, $header, $opts);

View File

@@ -36,7 +36,7 @@ function search() {
}
if(textValue != null || uriValue != null) {
if(textValue != null && uriValue != null) {
if(textValue.match(regexMatch) != null ||
uriValue.hostname.match(regexMatch) ||