forked from RSS-Bridge/rss-bridge
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[PatreonBridge] Add new bridge (RSS-Bridge#1254)
* [PatreonBridge] Add new bridge * [PatreonBridge] Add UID to articles Patreon changes post URLs when the post title is updated, so set a UID based on the post ID instead.
- Loading branch information
Showing
1 changed file
with
203 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,203 @@ | ||
<?php | ||
class PatreonBridge extends BridgeAbstract { | ||
const NAME = 'Patreon Bridge'; | ||
const URI = 'https://www.patreon.com/'; | ||
const CACHE_TIMEOUT = 300; // 5min | ||
const DESCRIPTION = 'Returns posts by creators on Patreon'; | ||
const MAINTAINER = 'Roliga'; | ||
const PARAMETERS = array( array( | ||
'creator' => array( | ||
'name' => 'Creator', | ||
'type' => 'text', | ||
'required' => true, | ||
'title' => 'Creator name as seen in their page URL' | ||
) | ||
)); | ||
|
||
public function collectData(){ | ||
$html = getSimpleHTMLDOMCached($this->getURI(), 86400) | ||
or returnServerError('Failed to load creator page at ' . $this->getURI()); | ||
$regex = '#/api/campaigns/([0-9]+)#'; | ||
if(preg_match($regex, $html->save(), $matches) > 0) { | ||
$campaign_id = $matches[1]; | ||
} else { | ||
returnServerError('Could not find campaign ID'); | ||
} | ||
|
||
$query = array( | ||
'include' => implode(',', array( | ||
'user', | ||
'attachments', | ||
'user_defined_tags', | ||
//'campaign', | ||
//'poll.choices', | ||
//'poll.current_user_responses.user', | ||
//'poll.current_user_responses.choice', | ||
//'poll.current_user_responses.poll', | ||
//'access_rules.tier.null', | ||
//'images.null', | ||
//'audio.null' | ||
)), | ||
'fields' => array( | ||
'post' => implode(',', array( | ||
//'change_visibility_at', | ||
//'comment_count', | ||
'content', | ||
//'current_user_can_delete', | ||
//'current_user_can_view', | ||
//'current_user_has_liked', | ||
//'embed', | ||
'image', | ||
//'is_paid', | ||
//'like_count', | ||
//'min_cents_pledged_to_view', | ||
//'patreon_url', | ||
//'patron_count', | ||
//'pledge_url', | ||
//'post_file', | ||
//'post_metadata', | ||
//'post_type', | ||
'published_at', | ||
'teaser_text', | ||
//'thumbnail_url', | ||
'title', | ||
//'upgrade_url', | ||
'url', | ||
//'was_posted_by_campaign_owner' | ||
)), | ||
'user' => implode(',', array( | ||
//'image_url', | ||
'full_name', | ||
//'url' | ||
)) | ||
), | ||
'filter' => array( | ||
'contains_exclusive_posts' => true, | ||
'is_draft' => false, | ||
'campaign_id' => $campaign_id | ||
), | ||
'sort' => '-published_at' | ||
); | ||
$posts = $this->apiGet('posts', $query); | ||
|
||
foreach($posts->data as $post) { | ||
$item = array( | ||
'uri' => $post->attributes->url, | ||
'title' => $post->attributes->title, | ||
'timestamp' => $post->attributes->published_at, | ||
'content' => '', | ||
'uid' => 'patreon.com/' . $post->id | ||
); | ||
|
||
$user = $this->findInclude($posts, | ||
'user', | ||
$post->relationships->user->data->id); | ||
$item['author'] = $user->full_name; | ||
|
||
if(isset($post->attributes->image)) | ||
$item['content'] .= '<p><a href="' | ||
. $post->attributes->url | ||
. '"><img src="' | ||
. $post->attributes->image->thumb_url | ||
. '" /></a></p>'; | ||
|
||
if(isset($post->attributes->content)) { | ||
$item['content'] .= $post->attributes->content; | ||
} elseif (isset($post->attributes->teaser_text)) { | ||
$item['content'] .= '<p>' | ||
. $post->attributes->teaser_text | ||
. '</p>'; | ||
} | ||
|
||
if(isset($post->relationships->user_defined_tags)) { | ||
$item['categories'] = array(); | ||
foreach($post->relationships->user_defined_tags->data as $tag) { | ||
$attrs = $this->findInclude($posts, 'post_tag', $tag->id); | ||
$item['categories'][] = $attrs->value; | ||
} | ||
} | ||
|
||
if(isset($post->relationships->attachments)) { | ||
$item['enclosures'] = array(); | ||
foreach($post->relationships->attachments->data as $attachment) { | ||
$attrs = $this->findInclude($posts, 'attachment', $attachment->id); | ||
$item['enclosures'][] = $attrs->url; | ||
} | ||
} | ||
|
||
$this->items[] = $item; | ||
} | ||
} | ||
|
||
/* | ||
* Searches the "included" array in an API response and returns attributes | ||
* for the first match. | ||
*/ | ||
private function findInclude($data, $type, $id) { | ||
foreach($data->included as $include) | ||
if($include->type === $type && $include->id === $id) | ||
return $include->attributes; | ||
} | ||
|
||
private function apiGet($endpoint, $query_data = array()) { | ||
$query_data['json-api-version'] = 1.0; | ||
$query_data['json-api-use-default-includes'] = 0; | ||
|
||
$url = 'https://www.patreon.com/api/' | ||
. $endpoint | ||
. '?' | ||
. http_build_query($query_data); | ||
|
||
/* | ||
* Accept-Language header and the CURL cipher list are for bypassing the | ||
* Cloudflare anti-bot protection on the Patreon API. If this ever breaks, | ||
* here are some other project that also deal with this: | ||
* https://github.com/mikf/gallery-dl/issues/342 | ||
* https://github.com/daemionfox/patreon-feed/issues/7 | ||
* https://www.patreondevelopers.com/t/api-returning-cloudflare-challenge/2025 | ||
* https://github.com/splitbrain/patreon-rss/issues/4 | ||
*/ | ||
$header = array( | ||
'Accept-Language: en-US', | ||
'Content-Type: application/json' | ||
); | ||
$opts = array( | ||
CURLOPT_SSL_CIPHER_LIST => implode(':', array( | ||
'DEFAULT', | ||
'!DHE-RSA-CHACHA20-POLY1305' | ||
)) | ||
); | ||
|
||
$data = json_decode(getContents($url, $header, $opts)) | ||
or returnServerError('API request to "' . $url . '" failed.'); | ||
|
||
return $data; | ||
} | ||
|
||
public function getName(){ | ||
if(!is_null($this->getInput('creator'))) | ||
return $this->getInput('creator') . ' posts'; | ||
|
||
return parent::getName(); | ||
} | ||
|
||
public function getURI(){ | ||
if(!is_null($this->getInput('creator'))) | ||
return self::URI . $this->getInput('creator'); | ||
|
||
return parent::getURI(); | ||
} | ||
|
||
public function detectParameters($url){ | ||
$params = array(); | ||
|
||
// Matches e.g. https://www.patreon.com/SomeCreator | ||
$regex = '/^(https?:\/\/)?(www\.)?patreon\.com\/([^\/&?\n]+)/'; | ||
if(preg_match($regex, $url, $matches) > 0) { | ||
$params['creator'] = urldecode($matches[3]); | ||
return $params; | ||
} | ||
|
||
return null; | ||
} | ||
} |