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'] .= '

'; if(isset($post->attributes->content)) { $item['content'] .= $post->attributes->content; } elseif (isset($post->attributes->teaser_text)) { $item['content'] .= '

' . $post->attributes->teaser_text . '

'; } 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; } }