diff --git a/KemonoCoomerBridge.php b/KemonoCoomerBridge.php new file mode 100644 index 0000000..f9368cf --- /dev/null +++ b/KemonoCoomerBridge.php @@ -0,0 +1,283 @@ +getInput('service'); + if ($service && in_array($service, ['onlyfans', 'fansly', 'candfans'])) { + return 'https://www.google.com/s2/favicons?domain=coomer.st&sz=32'; + } + return 'https://www.google.com/s2/favicons?domain=kemono.cr&sz=32'; + } + + const PARAMETERS = [ + [ + 'service' => [ + 'name' => 'Service', + 'type' => 'list', + 'required' => true, + 'values' => [ + 'Patreon' => 'patreon', + 'Fanbox' => 'fanbox', + 'Fantia' => 'fantia', + 'Gumroad' => 'gumroad', + 'SubscribeStar' => 'subscribestar', + 'Discord' => 'discord', + 'DLsite' => 'dlsite', + 'Boosty' => 'boosty', + 'Afdian' => 'afdian', + 'OnlyFans' => 'onlyfans', + 'Fansly' => 'fansly', + 'CandFans' => 'candfans' + ], + 'defaultValue' => 'patreon' + ], + 'user' => [ + 'name' => 'User ID', + 'type' => 'text', + 'required' => true, + 'title' => 'The creator\'s user ID (numeric or username)', + 'exampleValue' => '6889522' + ], + 'limit' => [ + 'name' => 'Limit', + 'type' => 'number', + 'required' => false, + 'title' => 'Maximum number of posts to return (default: 25, max: 50)', + 'defaultValue' => 25 + ] + ] + ]; + + private $creatorName = null; + + /** + * Check if the selected service is a Coomer service + */ + private function isCoomer(): bool + { + $service = $this->getInput('service') ?? ''; + return in_array($service, ['onlyfans', 'fansly', 'candfans']); + } + + /** + * Get the base URI based on the service (Kemono or Coomer) + */ + private function getBaseURI(): string + { + if ($this->isCoomer()) { + return 'https://coomer.st/'; + } + return self::URI; + } + + /** + * Get the CDN domain based on the service + */ + private function getCDNDomain(): string + { + if ($this->isCoomer()) { + return 'https://n1.coomer.st/data'; + } + return 'https://n1.kemono.cr/data'; + } + + public function collectData() + { + $service = $this->getInput('service'); + $user = $this->getInput('user'); + $limit = min((int)$this->getInput('limit'), 50); + + // Fetch user profile to get creator name + $this->fetchUserProfile($service, $user); + + // Build API URL + $apiUrl = $this->getBaseURI() . "api/v1/{$service}/user/{$user}/posts"; + + // Fetch posts with required header to bypass DDoS protection + $headers = ['Accept: text/css']; + $json = getContents($apiUrl, $headers); + $posts = json_decode($json, true); + + if (!is_array($posts)) { + throw new \Exception('Invalid API response: expected array of posts'); + } + + // Process posts (limited by user preference) + $count = 0; + foreach ($posts as $post) { + if ($count >= $limit) { + break; + } + + $item = $this->parsePost($post, $service, $user); + if ($item) { + $this->items[] = $item; + $count++; + } + } + } + + /** + * Parse a post from the API response into an RSS item + */ + private function parsePost(array $post, string $service, string $user): ?array + { + $postId = $post['id'] ?? null; + if (!$postId) { + return null; + } + + // Build post URL + $postUrl = $this->getBaseURI() . "{$service}/user/{$user}/post/{$postId}"; + + // Get title (with fallback) + $title = $post['title'] ?? 'Untitled Post'; + if (empty(trim($title))) { + $title = 'Untitled Post'; + } + + // Build content with embedded images + $content = $this->buildPostContent($post); + + // Parse timestamp + $timestamp = null; + if (isset($post['published'])) { + $timestamp = strtotime($post['published']); + } + + // Build item + $item = [ + 'title' => $title, + 'uri' => $postUrl, + 'timestamp' => $timestamp, + 'content' => $content, + 'uid' => $postId, + 'enclosures' => [] + ]; + + // Add author if we have creator name + if ($this->creatorName) { + $item['author'] = $this->creatorName; + } + + // Add main file as enclosure + if (isset($post['file']['path'])) { + $fileUrl = $this->getCDNDomain() . $post['file']['path']; + $item['enclosures'][] = $fileUrl; + } + + // Add attachments as enclosures + if (isset($post['attachments']) && is_array($post['attachments'])) { + foreach ($post['attachments'] as $attachment) { + if (isset($attachment['path'])) { + $attachmentUrl = $this->getCDNDomain() . $attachment['path']; + $item['enclosures'][] = $attachmentUrl; + } + } + } + + return $item; + } + + /** + * Build HTML content for the post with embedded images + */ + private function buildPostContent(array $post): string + { + $html = ''; + + // Add main image if present + if (isset($post['file']['path'])) { + $imageUrl = $this->getCDNDomain() . $post['file']['path']; + $imageName = $post['file']['name'] ?? 'Image'; + $html .= '