[ 'name' => 'Bluesky Data Source', 'type' => 'list', 'defaultValue' => 'Profile', 'values' => [ 'Profile' => 'getAuthorFeed', ], 'title' => 'Select the type of data source to fetch from Bluesky.' ], 'handle' => [ 'name' => 'User Handle', 'type' => 'text', 'required' => true, 'exampleValue' => 'jackdodo.bsky.social', 'title' => 'Handle found in URL' ], 'filter' => [ 'name' => 'Filter', 'type' => 'list', 'defaultValue' => 'posts_and_author_threads', 'values' => [ 'posts_and_author_threads' => 'posts_and_author_threads', 'posts_with_replies' => 'posts_with_replies', 'posts_no_replies' => 'posts_no_replies', 'posts_with_media' => 'posts_with_media', ], 'title' => 'Combinations of post/repost types to include in response.' ] ] ]; private $profile; public function getName() { if (isset($this->profile)) { return sprintf('%s (@%s) - Bluesky', $this->profile['displayName'], $this->profile['handle']); } return parent::getName(); } public function getURI() { if (isset($this->profile)) { return self::URI . '/profile/' . $this->profile['handle']; } return parent::getURI(); } public function getIcon() { if (isset($this->profile)) { return $this->profile['avatar']; } return parent::getIcon(); } public function getDescription() { if (isset($this->profile)) { return $this->profile['description']; } return parent::getDescription(); } private function parseExternal($external, $did) { $description = ''; $externalUri = $external['uri']; $externalTitle = htmlspecialchars($external['title'], ENT_QUOTES, 'UTF-8'); $externalDescription = htmlspecialchars($external['description'], ENT_QUOTES, 'UTF-8'); $thumb = $external['thumb'] ?? null; if (preg_match('/youtube\.com\/watch\?v=([^\&\?\/]+)/', $externalUri, $id) || preg_match('/youtu\.be\/([^\&\?\/]+)/', $externalUri, $id)) { $videoId = $id[1]; $description .= "
External Link: $externalTitle
"; $description .= ""; } else { $description .= "External Link: $externalTitle
"; $description .= "$externalDescription
"; if ($thumb) { $thumbUrl = 'https://cdn.bsky.app/img/feed_thumbnail/plain/' . $did . '/' . $thumb['ref']['$link'] . '@jpeg'; $description .= ""; } } return $description; } private function textToDescription($text) { $text = nl2br(htmlspecialchars($text, ENT_QUOTES, 'UTF-8')); $text = preg_replace('/(https?:\/\/[^\s]+)/i', '$1', $text); return $text; } public function collectData() { $handle = $this->getInput('handle'); $filter = $this->getInput('filter') ?: 'posts_and_author_threads'; $did = $this->resolveHandle($handle); $this->profile = $this->getProfile($did); $authorFeed = $this->getAuthorFeed($did, $filter); foreach ($authorFeed['feed'] as $post) { $item = []; $item['uri'] = self::URI . '/profile/' . $post['post']['author']['handle'] . '/post/' . explode('app.bsky.feed.post/', $post['post']['uri'])[1]; $item['title'] = strtok($post['post']['record']['text'], "\n"); $item['timestamp'] = strtotime($post['post']['record']['createdAt']); $item['author'] = $this->profile['displayName']; $description = $this->textToDescription($post['post']['record']['text']); // Retrieve DID for constructing image URLs $authorDid = $post['post']['author']['did']; if (isset($post['post']['record']['embed']['$type']) && $post['post']['record']['embed']['$type'] === 'app.bsky.embed.external') { $description .= $this->parseExternal($post['post']['record']['embed']['external'], $authorDid); } if (isset($post['post']['record']['embed']['$type']) && $post['post']['record']['embed']['$type'] === 'app.bsky.embed.video') { $thumbnail = $post['post']['embed']['thumbnail'] ?? null; if ($thumbnail) { $itemUri = self::URI . '/profile/' . $post['post']['author']['handle'] . '/post/' . explode('app.bsky.feed.post/', $post['post']['uri'])[1]; $description .= ""; } } if (isset($post['post']['record']['embed']['$type']) && $post['post']['record']['embed']['$type'] === 'app.bsky.embed.recordWithMedia#view') { $thumbnail = $post['post']['embed']['media']['thumbnail'] ?? null; $playlist = $post['post']['embed']['media']['playlist'] ?? null; if ($thumbnail) { $description .= "'; } } if (!empty($post['post']['record']['embed']['images'])) { foreach ($post['post']['record']['embed']['images'] as $image) { $linkRef = $image['image']['ref']['$link']; $thumbnailUrl = $this->resolveThumbnailUrl($authorDid, $linkRef); $fullsizeUrl = $this->resolveFullsizeUrl($authorDid, $linkRef); $description .= "