use Friendica\Model\Post;
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
+use Friendica\Object\Image;
use Friendica\Protocol\Activity;
use Friendica\Protocol\Relay;
use Friendica\Util\DateTimeFormat;
const BLUESKY_DEFAULT_POLL_INTERVAL = 10; // given in minutes
const BLUESKY_HOST = 'https://bsky.app'; // Hard wired until Bluesky will run on multiple systems
+const BLUESKY_IMAGE_SIZE = [1000000, 500000, 100000, 50000];
function bluesky_install()
{
return;
}
+ // Try to fetch the language from the post itself
+ if (!empty($item['language'])) {
+ $language = array_key_first(json_decode($item['language'], true));
+ } else {
+ $language = '';
+ }
+
$did = DI::pConfig()->get($uid, 'bluesky', 'did');
$urls = bluesky_get_urls(Post\Media::removeFromBody($item['body']));
$item['body'] = $urls['body'];
$record = [
'text' => $facets['body'],
+ '$type' => 'app.bsky.feed.post',
'createdAt' => DateTimeFormat::utcNow(DateTimeFormat::ATOM),
- '$type' => 'app.bsky.feed.post'
];
+ if (!empty($language)) {
+ $record['langs'] = [$language];
+ }
+
if (!empty($facets['facets'])) {
$record['facets'] = $facets['facets'];
}
if ($key == count($msg['parts']) - 1) {
$record = bluesky_add_embed($uid, $msg, $record);
+ if (empty($record)) {
+ if (Worker::getRetrial() < 3) {
+ Worker::defer();
+ }
+ return;
+ }
}
$post = [
$parent = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post);
if (empty($parent)) {
+ if ($part == 0) {
+ Worker::defer();
+ }
return;
}
Logger::debug('Posting done', ['return' => $parent]);
function bluesky_get_urls(string $body): array
{
- // Remove all hashtags and mentions
- $body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '', $body);
+ // Remove all hashtag and mention links
+ $body = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $body);
+ $body = BBCode::expandVideoLinks($body);
$urls = [];
// Search for pure links
if (($msg['type'] != 'link') && !empty($msg['images'])) {
$images = [];
foreach ($msg['images'] as $image) {
- $photo = Photo::selectFirst(['resource-id'], ['id' => $image['id']]);
- $photo = Photo::selectFirst([], ["`resource-id` = ? AND `scale` > ?", $photo['resource-id'], 0], ['order' => ['scale']]);
+ if (count($images) == 4) {
+ continue;
+ }
+ $photo = Photo::selectFirst([], ['id' => $image['id']]);
$blob = bluesky_upload_blob($uid, $photo);
- if (!empty($blob) && count($images) < 4) {
- $images[] = ['alt' => $image['description'] ?? '', 'image' => $blob];
+ if (empty($blob)) {
+ return [];
}
+ $images[] = ['alt' => $image['description'] ?? '', 'image' => $blob];
}
if (!empty($images)) {
$record['embed'] = ['$type' => 'app.bsky.embed.images', 'images' => $images];
'$type' => 'app.bsky.embed.external',
'external' => [
'uri' => $msg['url'],
- 'title' => $msg['title'],
- 'description' => $msg['description'],
+ 'title' => $msg['title'] ?? '',
+ 'description' => $msg['description'] ?? '',
]
];
if (!empty($msg['image'])) {
function bluesky_upload_blob(int $uid, array $photo): ?stdClass
{
+ $retrial = Worker::getRetrial();
$content = Photo::getImageForPhoto($photo);
+
+ $picture = new Image($content, $photo['type']);
+ $height = $picture->getHeight();
+ $width = $picture->getWidth();
+ $size = strlen($content);
+
+ $picture = Photo::resizeToFileSize($picture, BLUESKY_IMAGE_SIZE[$retrial]);
+ $new_height = $picture->getHeight();
+ $new_width = $picture->getWidth();
+ $content = $picture->asString();
+ $new_size = strlen($content);
+
+ Logger::info('Uploading', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
+
$data = bluesky_post($uid, '/xrpc/com.atproto.repo.uploadBlob', $content, ['Content-type' => $photo['type'], 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]);
if (empty($data)) {
+ Logger::info('Uploading failed', ['uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
return null;
}
- Logger::debug('Uploaded blob', ['return' => $data]);
+ Logger::debug('Uploaded blob', ['return' => $data, 'uid' => $uid, 'retrial' => $retrial, 'height' => $new_height, 'width' => $new_width, 'size' => $new_size, 'orig-height' => $height, 'orig-width' => $width, 'orig-size' => $size]);
return $data->blob;
}
$data = Item::insert($item);
Logger::debug('Got like', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
} else {
- Logger::info('Thread parent not found', ['uid' => $uid, 'parent' => $$item['thr-parent'], 'uri' => $uri]);
+ Logger::info('Thread parent not found', ['uid' => $uid, 'parent' => $item['thr-parent'], 'uri' => $uri]);
}
break;
$data = Item::insert($item);
Logger::debug('Got repost', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
} else {
- Logger::info('Thread parent not found', ['uid' => $uid, 'parent' => $$item['thr-parent'], 'uri' => $uri]);
+ Logger::info('Thread parent not found', ['uid' => $uid, 'parent' => $item['thr-parent'], 'uri' => $uri]);
}
break;
return;
}
+ $feeddata = bluesky_xrpc_get($uid, 'app.bsky.feed.getFeedGenerator', ['feed' => $feed]);
+ if (!empty($feeddata)) {
+ $feedurl = $feeddata->view->uri;
+ $feedname = $feeddata->view->displayName;
+ } else {
+ $feedurl = $feed;
+ $feedname = $feed;
+ }
+
foreach (array_reverse($data->feed) as $entry) {
- if (!Relay::isWantedLanguage($entry->post->record->text)) {
+ $contact = bluesky_get_contact($entry->post->author, 0, $uid);
+ $languages = $entry->post->record->langs ?? [];
+
+ if (!Relay::isWantedLanguage($entry->post->record->text, 0, $contact['id'] ?? 0, $languages)) {
Logger::debug('Unwanted language detected', ['text' => $entry->post->record->text]);
continue;
}
- bluesky_process_post($entry->post, $uid, Item::PR_TAG, 0);
+ $id = bluesky_process_post($entry->post, $uid, Item::PR_TAG, 0);
+ if (!empty($id)) {
+ $post = Post::selectFirst(['uri-id'], ['id' => $id]);
+ if (!empty($post['uri-id'])) {
+ $stored = Post\Category::storeFileByURIId($post['uri-id'], $uid, Post\Category::SUBCRIPTION, $feedname, $feedurl);
+ Logger::debug('Stored tag subscription for user', ['uri-id' => $post['uri-id'], 'uid' => $uid, 'name' => $feedname, 'url' => $feedurl, 'stored' => $stored]);
+ } else {
+ Logger::notice('Post not found', ['id' => $id, 'entry' => $entry]);
+ }
+ }
if (!empty($entry->reason)) {
bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid);
}
{
$uri = bluesky_get_uri($post);
- if (Post::exists(['uri' => $uri, 'uid' => $uid]) || Post::exists(['extid' => $uri, 'uid' => $uid])) {
- return 0;
+ if ($id = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $uid])) {
+ return $id['id'];
+ }
+
+ if ($id = Post::selectFirst(['id'], ['extid' => $uri, 'uid' => $uid])) {
+ return $id['id'];
}
Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid, 'root' => $post->record->reply->root ?? '']);
'author-link' => $contact['url'],
'author-avatar' => $contact['avatar'],
'plink' => $contact['alias'] . '/post/' . $parts->rkey,
+ 'source' => json_encode($post),
];
$item['uri-id'] = ItemURI::getIdByURI($uri);
$item['body'] = bluesky_get_text($record);
$item['created'] = DateTimeFormat::utc($record->createdAt, DateTimeFormat::MYSQL);
+ $item['transmitted-languages'] = $record->langs ?? [];
return $item;
}
function bluesky_get_text(stdClass $record): string
{
- $text = $record->text;
+ $text = $record->text ?? '';
if (empty($record->facets)) {
return $text;
$uri = bluesky_get_uri($embed->record);
$shared = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => $item['uid']]);
if (empty($shared)) {
+ if (empty($embed->record->value)) {
+ Logger::info('Record has got no value', ['record' => $embed->record]);
+ break;
+ }
$shared = bluesky_get_header($embed->record, $uri, 0, $fetch_uid);
$shared = bluesky_get_content($shared, $embed->record->value, $uri, $item['uid'], $level);
if (!empty($shared)) {
$shared = bluesky_add_media($single, $shared, $fetch_uid, $level);
}
}
- $id = Item::insert($shared);
- $shared = Post::selectFirst(['uri-id'], ['id' => $id]);
+ Item::insert($shared);
}
}
- if (!empty($shared)) {
+ if (!empty($shared['uri-id'])) {
$item['quote-uri-id'] = $shared['uri-id'];
}
break;
$shared = bluesky_get_header($embed->record->record, $uri, 0, $fetch_uid);
$shared = bluesky_get_content($shared, $embed->record->record->value, $uri, $item['uid'], $level);
if (!empty($shared)) {
- if (!empty($embed->record->embeds)) {
+ if (!empty($embed->record->record->embeds)) {
foreach ($embed->record->record->embeds as $single) {
$shared = bluesky_add_media($single, $shared, $fetch_uid, $level);
}
}
-
- if (!empty($embed->media)) {
- bluesky_add_media($embed->media, $item, $fetch_uid, $level);
- }
-
- $id = Item::insert($shared);
- $shared = Post::selectFirst(['uri-id'], ['id' => $id]);
+ Item::insert($shared);
}
}
- if (!empty($shared)) {
+ if (!empty($shared['uri-id'])) {
$item['quote-uri-id'] = $shared['uri-id'];
}
+
+ if (!empty($embed->media)) {
+ $item = bluesky_add_media($embed->media, $item, $fetch_uid, $level);
+ }
break;
default:
function bluesky_get_uri(stdClass $post): string
{
+ if (empty($post->cid)) {
+ Logger::info('Invalid URI', ['post' => $post]);
+ return '';
+ }
return $post->uri . ':' . $post->cid;
}
if (++$level > 100) {
Logger::info('Recursion level too deep', ['level' => $level, 'uid' => $uid, 'uri' => $uri, 'fallback' => $fallback]);
// When the level is too deep we will fallback to the parent uri.
- // Allthough the threading won't be correct, we at least had stored all posts and won't try again
+ // Allthough the threading won't be correct, we at least had stored all posts and won't try again
return $fallback;
}
Logger::info('Thread was not fetched', ['level' => $level, 'uid' => $uid, 'uri' => $uri, 'fallback' => $fallback]);
return $fallback;
}
-
- Logger::debug('Reply count', ['replies' => $data->thread->post->replyCount, 'level' => $level, 'uid' => $uid, 'uri' => $uri]);
+
+ Logger::debug('Reply count', ['level' => $level, 'uid' => $uid, 'uri' => $uri]);
if ($causer != 0) {
$cdata = Contact::getPublicAndUserContactID($causer, $uid);
function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $level): string
{
+ if (empty($thread->post)) {
+ Logger::info('Invalid post', ['post' => $thread]);
+ return '';
+ }
$uri = bluesky_get_uri($thread->post);
+
$fetched_uri = bluesky_fetch_post($uri, $uid);
if (empty($fetched_uri)) {
Logger::debug('Process missing post', ['uri' => $uri]);
}
return json_decode($curlResult->getBody());
-}
\ No newline at end of file
+}