Merge pull request 'Bluesky: Tasks are now done by workers' (#1399) from heluecht/friendica-addons:bluesky-worker into develop

Reviewed-on: https://git.friendi.ca/friendica/friendica-addons/pulls/1399
This commit is contained in:
Hypolite Petovan 2023-06-19 00:29:53 +02:00
commit aa0d829de6
4 changed files with 187 additions and 85 deletions

View file

@ -9,6 +9,9 @@
* Currently technical issues in the core: * Currently technical issues in the core:
* - Outgoing mentions * - Outgoing mentions
* *
* At some point in time:
* - Sending Quote shares https://atproto.com/lexicons/app-bsky-embed#appbskyembedrecord and https://atproto.com/lexicons/app-bsky-embed#appbskyembedrecordwithmedia
*
* Possibly not possible: * Possibly not possible:
* - only fetch new posts * - only fetch new posts
* *
@ -18,8 +21,6 @@
* *
* Possibly interesting: * Possibly interesting:
* - https://atproto.com/lexicons/com-atproto-label#comatprotolabelsubscribelabels * - https://atproto.com/lexicons/com-atproto-label#comatprotolabelsubscribelabels
* - https://atproto.com/lexicons/com-atproto-repo#comatprotorepoputrecord
* - https://atproto.com/lexicons/app-bsky-embed#appbskyembedrecordwithmedia
*/ */
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
@ -117,7 +118,7 @@ function bluesky_probe_detect(array &$hookData)
return; return;
} }
$data = bluesky_get($pconfig['uid'], '/xrpc/app.bsky.actor.getProfile?actor=' . $did, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $token]]]); $data = bluesky_xrpc_get($pconfig['uid'], 'app.bsky.actor.getProfile', ['actor' => $did]);
if (empty($data)) { if (empty($data)) {
return; return;
} }
@ -162,7 +163,7 @@ function bluesky_item_by_link(array &$hookData)
$uri = 'at://' . $did . '/app.bsky.feed.post/' . $matches[2]; $uri = 'at://' . $did . '/app.bsky.feed.post/' . $matches[2];
$uri = bluesky_fetch_missing_post($uri, $hookData['uid'], 0); $uri = bluesky_fetch_missing_post($uri, $hookData['uid'], 0, 0);
Logger::debug('Got post', ['profile' => $matches[1], 'cid' => $matches[2], 'result' => $uri]); Logger::debug('Got post', ['profile' => $matches[1], 'cid' => $matches[2], 'result' => $uri]);
if (!empty($uri)) { if (!empty($uri)) {
$item = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $hookData['uid']]); $item = Post::selectFirst(['id'], ['uri' => $uri, 'uid' => $hookData['uid']]);
@ -211,7 +212,7 @@ function bluesky_follow(array &$hook_data)
'record' => $record 'record' => $record
]; ];
$activity = bluesky_post($hook_data['uid'], '/xrpc/com.atproto.repo.createRecord', json_encode($post), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . $token]]); $activity = bluesky_xrpc_post($hook_data['uid'], 'com.atproto.repo.createRecord', $post);
if (!empty($activity->uri)) { if (!empty($activity->uri)) {
$hook_data['contact'] = $contact; $hook_data['contact'] = $contact;
Logger::debug('Successfully start following', ['url' => $contact['url'], 'uri' => $activity->uri]); Logger::debug('Successfully start following', ['url' => $contact['url'], 'uri' => $activity->uri]);
@ -229,7 +230,7 @@ function bluesky_unfollow(array &$hook_data)
return; return;
} }
$data = bluesky_get($hook_data['uid'], '/xrpc/app.bsky.actor.getProfile?actor=' . $hook_data['contact']['url'], HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $token]]]); $data = bluesky_xrpc_get($hook_data['uid'], 'app.bsky.actor.getProfile', ['actor' => $hook_data['contact']['url']]);
if (empty($data->viewer) || empty($data->viewer->following)) { if (empty($data->viewer) || empty($data->viewer->following)) {
return; return;
} }
@ -264,7 +265,7 @@ function bluesky_block(array &$hook_data)
'record' => $record 'record' => $record
]; ];
$activity = bluesky_post($hook_data['uid'], '/xrpc/com.atproto.repo.createRecord', json_encode($post), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . $token]]); $activity = bluesky_xrpc_post($hook_data['uid'], 'com.atproto.repo.createRecord', $post);
if (!empty($activity->uri)) { if (!empty($activity->uri)) {
$cdata = Contact::getPublicAndUserContactID($hook_data['contact']['id'], $hook_data['uid']); $cdata = Contact::getPublicAndUserContactID($hook_data['contact']['id'], $hook_data['uid']);
if (!empty($cdata['user'])) { if (!empty($cdata['user'])) {
@ -285,7 +286,7 @@ function bluesky_unblock(array &$hook_data)
return; return;
} }
$data = bluesky_get($hook_data['uid'], '/xrpc/app.bsky.actor.getProfile?actor=' . $hook_data['contact']['url'], HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . $token]]]); $data = bluesky_xrpc_get($hook_data['uid'], 'app.bsky.actor.getProfile', ['actor' => $hook_data['contact']['url']]);
if (empty($data->viewer) || empty($data->viewer->blocking)) { if (empty($data->viewer) || empty($data->viewer->blocking)) {
return; return;
} }
@ -419,22 +420,17 @@ function bluesky_cron()
} }
} }
Logger::notice('importing timeline - start', ['user' => $pconfig['uid']]); // Refresh the token now, so that it doesn't need to be refreshed in parallel by the following workers
bluesky_fetch_timeline($pconfig['uid']); bluesky_get_token($pconfig['uid']);
Logger::notice('importing timeline - done', ['user' => $pconfig['uid']]);
Logger::notice('importing notifications - start', ['user' => $pconfig['uid']]); Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_timeline.php', $pconfig['uid']);
bluesky_fetch_notifications($pconfig['uid']); Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_notifications.php', $pconfig['uid']);
Logger::notice('importing notifications - done', ['user' => $pconfig['uid']]);
if (DI::pConfig()->get($pconfig['uid'], 'bluesky', 'import_feeds')) { if (DI::pConfig()->get($pconfig['uid'], 'bluesky', 'import_feeds')) {
Logger::notice('importing feeds - start', ['user' => $pconfig['uid']]);
$feeds = bluesky_get_feeds($pconfig['uid']); $feeds = bluesky_get_feeds($pconfig['uid']);
foreach ($feeds as $feed) { foreach ($feeds as $feed) {
Logger::debug('Importing feed', ['user' => $pconfig['uid'], 'feed' => $feed]); Worker::add(['priority' => Worker::PRIORITY_MEDIUM, 'force_priority' => true], 'addon/bluesky/bluesky_feed.php', $pconfig['uid'], $feed);
bluesky_fetch_feed($pconfig['uid'], $feed);
} }
Logger::notice('importing feeds - done', ['user' => $pconfig['uid']]);
} }
} }
@ -594,7 +590,7 @@ function bluesky_create_activity(array $item, stdClass $parent = null)
]; ];
} }
$activity = bluesky_post($uid, '/xrpc/com.atproto.repo.createRecord', json_encode($post), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . $token]]); $activity = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post);
if (empty($activity)) { if (empty($activity)) {
return; return;
} }
@ -645,7 +641,7 @@ function bluesky_create_post(array $item, stdClass $root = null, stdClass $paren
'record' => $record 'record' => $record
]; ];
$parent = bluesky_post($uid, '/xrpc/com.atproto.repo.createRecord', json_encode($post), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . $token]]); $parent = bluesky_xrpc_post($uid, 'com.atproto.repo.createRecord', $post);
if (empty($parent)) { if (empty($parent)) {
return; return;
} }
@ -793,19 +789,18 @@ function bluesky_upload_blob(int $uid, array $photo): ?stdClass
function bluesky_delete_post(string $uri, int $uid) function bluesky_delete_post(string $uri, int $uid)
{ {
$token = bluesky_get_token($uid);
$parts = bluesky_get_uri_parts($uri); $parts = bluesky_get_uri_parts($uri);
if (empty($parts)) { if (empty($parts)) {
Logger::debug('No uri delected', ['uri' => $uri]); Logger::debug('No uri delected', ['uri' => $uri]);
return; return;
} }
bluesky_post($uid, '/xrpc/com.atproto.repo.deleteRecord', json_encode($parts), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . $token]]); bluesky_xrpc_post($uid, 'com.atproto.repo.deleteRecord', $parts);
Logger::debug('Deleted', ['parts' => $parts]); Logger::debug('Deleted', ['parts' => $parts]);
} }
function bluesky_fetch_timeline(int $uid) function bluesky_fetch_timeline(int $uid)
{ {
$data = bluesky_get($uid, '/xrpc/app.bsky.feed.getTimeline', HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]); $data = bluesky_xrpc_get($uid, 'app.bsky.feed.getTimeline');
if (empty($data)) { if (empty($data)) {
return; return;
} }
@ -815,7 +810,7 @@ function bluesky_fetch_timeline(int $uid)
} }
foreach (array_reverse($data->feed) as $entry) { foreach (array_reverse($data->feed) as $entry) {
bluesky_process_post($entry->post, $uid, Item::PR_NONE); bluesky_process_post($entry->post, $uid, Item::PR_NONE, 0);
if (!empty($entry->reason)) { if (!empty($entry->reason)) {
bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid); bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid);
} }
@ -856,6 +851,7 @@ function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
return; return;
} }
$item['guid'] = Item::guidFromUri($item['uri'], $contact['alias']);
$item['owner-name'] = $item['author-name']; $item['owner-name'] = $item['author-name'];
$item['owner-link'] = $item['author-link']; $item['owner-link'] = $item['author-link'];
$item['owner-avatar'] = $item['author-avatar']; $item['owner-avatar'] = $item['author-avatar'];
@ -867,11 +863,11 @@ function bluesky_process_reason(stdClass $reason, string $uri, int $uid)
function bluesky_fetch_notifications(int $uid) function bluesky_fetch_notifications(int $uid)
{ {
$result = bluesky_get($uid, '/xrpc/app.bsky.notification.listNotifications', HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]); $data = bluesky_xrpc_get($uid, 'app.bsky.notification.listNotifications');
if (empty($result->notifications)) { if (empty($data->notifications)) {
return; return;
} }
foreach ($result->notifications as $notification) { foreach ($data->notifications as $notification) {
$uri = bluesky_get_uri($notification); $uri = bluesky_get_uri($notification);
if (Post::exists(['uri' => $uri, 'uid' => $uid]) || Post::exists(['extid' => $uri, 'uid' => $uid])) { if (Post::exists(['uri' => $uri, 'uid' => $uid]) || Post::exists(['extid' => $uri, 'uid' => $uid])) {
Logger::debug('Notification already processed', ['uid' => $uid, 'reason' => $notification->reason, 'uri' => $uri, 'indexedAt' => $notification->indexedAt]); Logger::debug('Notification already processed', ['uid' => $uid, 'reason' => $notification->reason, 'uri' => $uri, 'indexedAt' => $notification->indexedAt]);
@ -884,9 +880,13 @@ function bluesky_fetch_notifications(int $uid)
$item['gravity'] = Item::GRAVITY_ACTIVITY; $item['gravity'] = Item::GRAVITY_ACTIVITY;
$item['body'] = $item['verb'] = Activity::LIKE; $item['body'] = $item['verb'] = Activity::LIKE;
$item['thr-parent'] = bluesky_get_uri($notification->record->subject); $item['thr-parent'] = bluesky_get_uri($notification->record->subject);
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id']); $item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], 0);
$result = Item::insert($item); if (!empty($item['thr-parent'])) {
Logger::debug('Got like', ['uid' => $uid, 'result' => $result, 'uri' => $uri]); $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]);
}
break; break;
case 'repost': case 'repost':
@ -894,9 +894,13 @@ function bluesky_fetch_notifications(int $uid)
$item['gravity'] = Item::GRAVITY_ACTIVITY; $item['gravity'] = Item::GRAVITY_ACTIVITY;
$item['body'] = $item['verb'] = Activity::ANNOUNCE; $item['body'] = $item['verb'] = Activity::ANNOUNCE;
$item['thr-parent'] = bluesky_get_uri($notification->record->subject); $item['thr-parent'] = bluesky_get_uri($notification->record->subject);
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id']); $item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], 0);
$result = Item::insert($item); if (!empty($item['thr-parent'])) {
Logger::debug('Got repost', ['uid' => $uid, 'result' => $result, 'uri' => $uri]); $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]);
}
break; break;
case 'follow': case 'follow':
@ -905,18 +909,18 @@ function bluesky_fetch_notifications(int $uid)
break; break;
case 'mention': case 'mention':
$result = bluesky_process_post($notification, $uid, Item::PR_PUSHED); $data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0);
Logger::debug('Got mention', ['uid' => $uid, 'result' => $result, 'uri' => $uri]); Logger::debug('Got mention', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
break; break;
case 'reply': case 'reply':
$result = bluesky_process_post($notification, $uid, Item::PR_PUSHED); $data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0);
Logger::debug('Got reply', ['uid' => $uid, 'result' => $result, 'uri' => $uri]); Logger::debug('Got reply', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
break; break;
case 'quote': case 'quote':
$result = bluesky_process_post($notification, $uid, Item::PR_PUSHED); $data = bluesky_process_post($notification, $uid, Item::PR_PUSHED, 0);
Logger::debug('Got quote', ['uid' => $uid, 'result' => $result, 'uri' => $uri]); Logger::debug('Got quote', ['uid' => $uid, 'result' => $data, 'uri' => $uri]);
break; break;
default: default:
@ -928,7 +932,7 @@ function bluesky_fetch_notifications(int $uid)
function bluesky_fetch_feed(int $uid, string $feed) function bluesky_fetch_feed(int $uid, string $feed)
{ {
$data = bluesky_get($uid, '/xrpc/app.bsky.feed.getFeed?feed=' . $feed, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]); $data = bluesky_xrpc_get($uid, 'app.bsky.feed.getFeed', ['feed' => $feed]);
if (empty($data)) { if (empty($data)) {
return; return;
} }
@ -942,14 +946,14 @@ function bluesky_fetch_feed(int $uid, string $feed)
Logger::debug('Unwanted language detected', ['text' => $entry->post->record->text]); Logger::debug('Unwanted language detected', ['text' => $entry->post->record->text]);
continue; continue;
} }
bluesky_process_post($entry->post, $uid, Item::PR_TAG); bluesky_process_post($entry->post, $uid, Item::PR_TAG, 0);
if (!empty($entry->reason)) { if (!empty($entry->reason)) {
bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid); bluesky_process_reason($entry->reason, bluesky_get_uri($entry->post), $uid);
} }
} }
} }
function bluesky_process_post(stdClass $post, int $uid, int $post_reason): int function bluesky_process_post(stdClass $post, int $uid, int $post_reason, $level): int
{ {
$uri = bluesky_get_uri($post); $uri = bluesky_get_uri($post);
@ -957,14 +961,16 @@ function bluesky_process_post(stdClass $post, int $uid, int $post_reason): int
return 0; return 0;
} }
Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid]); Logger::debug('Importing post', ['uid' => $uid, 'indexedAt' => $post->indexedAt, 'uri' => $post->uri, 'cid' => $post->cid, 'root' => $post->record->reply->root ?? '']);
$item = bluesky_get_header($post, $uri, $uid, $uid); $item = bluesky_get_header($post, $uri, $uid, $uid);
$item = bluesky_get_content($item, $post->record, $uri, $uid, $level);
$item = bluesky_get_content($item, $post->record, $uid); if (empty($item)) {
return 0;
}
if (!empty($post->embed)) { if (!empty($post->embed)) {
$item = bluesky_add_media($post->embed, $item, $uid); $item = bluesky_add_media($post->embed, $item, $uid, $level);
} }
if (empty($item['post-reason'])) { if (empty($item['post-reason'])) {
@ -1008,21 +1014,36 @@ function bluesky_get_header(stdClass $post, string $uri, int $uid, int $fetch_ui
return $item; return $item;
} }
function bluesky_get_content(array $item, stdClass $record, int $uid): array function bluesky_get_content(array $item, stdClass $record, string $uri, int $uid, int $level): array
{ {
if (!empty($record->reply)) { if (empty($item)) {
$item['parent-uri'] = bluesky_get_uri($record->reply->root); return [];
$item['parent-uri'] = bluesky_fetch_missing_post($item['parent-uri'], $uid, $item['contact-id']);
$item['thr-parent'] = bluesky_get_uri($record->reply->parent);
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id']);
} }
$item['body'] = bluesky_get_text($record, $uid); if (!empty($record->reply)) {
$item['parent-uri'] = bluesky_get_uri($record->reply->root);
if ($item['parent-uri'] != $uri) {
$item['parent-uri'] = bluesky_fetch_missing_post($item['parent-uri'], $uid, $item['contact-id'], $level);
if (empty($item['parent-uri'])) {
return [];
}
}
$item['thr-parent'] = bluesky_get_uri($record->reply->parent);
if (!in_array($item['thr-parent'], [$uri, $item['parent-uri']])) {
$item['thr-parent'] = bluesky_fetch_missing_post($item['thr-parent'], $uid, $item['contact-id'], $level, $item['parent-uri']);
if (empty($item['thr-parent'])) {
return [];
}
}
}
$item['body'] = bluesky_get_text($record);
$item['created'] = DateTimeFormat::utc($record->createdAt, DateTimeFormat::MYSQL); $item['created'] = DateTimeFormat::utc($record->createdAt, DateTimeFormat::MYSQL);
return $item; return $item;
} }
function bluesky_get_text(stdClass $record, int $uid): string function bluesky_get_text(stdClass $record): string
{ {
$text = $record->text; $text = $record->text;
@ -1073,7 +1094,7 @@ function bluesky_get_text(stdClass $record, int $uid): string
return $text; return $text;
} }
function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid): array function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid, int $level): array
{ {
$type = '$type'; $type = '$type';
switch ($embed->$type) { switch ($embed->$type) {
@ -1106,12 +1127,11 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid): array
$shared = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => $item['uid']]); $shared = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => $item['uid']]);
if (empty($shared)) { if (empty($shared)) {
$shared = bluesky_get_header($embed->record, $uri, 0, $fetch_uid); $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)) { if (!empty($shared)) {
$shared = bluesky_get_content($shared, $embed->record->value, $item['uid']);
if (!empty($embed->record->embeds)) { if (!empty($embed->record->embeds)) {
foreach ($embed->record->embeds as $single) { foreach ($embed->record->embeds as $single) {
$shared = bluesky_add_media($single, $shared, $fetch_uid); $shared = bluesky_add_media($single, $shared, $fetch_uid, $level);
} }
} }
$id = Item::insert($shared); $id = Item::insert($shared);
@ -1128,17 +1148,16 @@ function bluesky_add_media(stdClass $embed, array $item, int $fetch_uid): array
$shared = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => $item['uid']]); $shared = Post::selectFirst(['uri-id'], ['uri' => $uri, 'uid' => $item['uid']]);
if (empty($shared)) { if (empty($shared)) {
$shared = bluesky_get_header($embed->record->record, $uri, 0, $fetch_uid); $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($shared)) {
$shared = bluesky_get_content($shared, $embed->record->record->value, $item['uid']);
if (!empty($embed->record->embeds)) { if (!empty($embed->record->embeds)) {
foreach ($embed->record->record->embeds as $single) { foreach ($embed->record->record->embeds as $single) {
$shared = bluesky_add_media($single, $shared, $fetch_uid); $shared = bluesky_add_media($single, $shared, $fetch_uid, $level);
} }
} }
if (!empty($embed->media)) { if (!empty($embed->media)) {
bluesky_add_media($embed->media, $item, $fetch_uid); bluesky_add_media($embed->media, $item, $fetch_uid, $level);
} }
$id = Item::insert($shared); $id = Item::insert($shared);
@ -1205,27 +1224,39 @@ function bluesky_get_uri_parts(string $uri): ?stdClass
return $class; return $class;
} }
function bluesky_fetch_missing_post(string $uri, int $uid, int $causer): string function bluesky_fetch_missing_post(string $uri, int $uid, int $causer, int $level, string $fallback = ''): string
{ {
$fetched_uri = bluesky_fetch_post($uri, $uid); $fetched_uri = bluesky_fetch_post($uri, $uid);
if (!empty($fetched_uri)) { if (!empty($fetched_uri)) {
return $fetched_uri; return $fetched_uri;
} }
Logger::debug('Fetch missing post', ['uri' => $uri]); 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
return $fallback;
}
$class = bluesky_get_uri_class($uri); $class = bluesky_get_uri_class($uri);
$fetch_uri = $class->uri; $fetch_uri = $class->uri;
$data = bluesky_get($uid, '/xrpc/app.bsky.feed.getPostThread?uri=' . urlencode($fetch_uri), HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]); Logger::debug('Fetch missing post', ['level' => $level, 'uid' => $uid, 'uri' => $uri]);
$data = bluesky_xrpc_get($uid, 'app.bsky.feed.getPostThread', ['uri' => $fetch_uri]);
if (empty($data)) { if (empty($data)) {
return ''; 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]);
if ($causer != 0) { if ($causer != 0) {
$cdata = Contact::getPublicAndUserContactID($causer, $uid); $cdata = Contact::getPublicAndUserContactID($causer, $uid);
} else {
$cdata = [];
} }
return bluesky_process_thread($data->thread, $uid, $cdata); return bluesky_process_thread($data->thread, $uid, $cdata, $level);
} }
function bluesky_fetch_post(string $uri, int $uid): string function bluesky_fetch_post(string $uri, int $uid): string
@ -1243,15 +1274,15 @@ function bluesky_fetch_post(string $uri, int $uid): string
return ''; return '';
} }
function bluesky_process_thread(stdClass $thread, int $uid, array $cdata): string function bluesky_process_thread(stdClass $thread, int $uid, array $cdata, int $level): string
{ {
$uri = bluesky_get_uri($thread->post); $uri = bluesky_get_uri($thread->post);
$fetched_uri = bluesky_fetch_post($uri, $uid); $fetched_uri = bluesky_fetch_post($uri, $uid);
if (empty($fetched_uri)) { if (empty($fetched_uri)) {
Logger::debug('Process missing post', ['uri' => $uri]); Logger::debug('Process missing post', ['uri' => $uri]);
$item = bluesky_get_header($thread->post, $uri, $uid, $uid); $item = bluesky_get_header($thread->post, $uri, $uid, $uid);
$item = bluesky_get_content($item, $thread->post->record, $uid); $item = bluesky_get_content($item, $thread->post->record, $uri, $uid, $level);
if (!empty($item)) {
$item['post-reason'] = Item::PR_FETCHED; $item['post-reason'] = Item::PR_FETCHED;
if (!empty($cdata['public'])) { if (!empty($cdata['public'])) {
@ -1259,17 +1290,26 @@ function bluesky_process_thread(stdClass $thread, int $uid, array $cdata): strin
} }
if (!empty($thread->post->embed)) { if (!empty($thread->post->embed)) {
$item = bluesky_add_media($thread->post->embed, $item, $uid); $item = bluesky_add_media($thread->post->embed, $item, $uid, $level);
} }
$id = Item::insert($item); $id = Item::insert($item);
if (!$id) {
Logger::info('Item has not not been stored', ['uri' => $uri]);
return '';
}
Logger::debug('Stored item', ['id' => $id, 'uri' => $uri]); Logger::debug('Stored item', ['id' => $id, 'uri' => $uri]);
} else {
Logger::info('Post has not not been fetched', ['uri' => $uri]);
return '';
}
} else { } else {
Logger::debug('Post exists', ['uri' => $uri]); Logger::debug('Post exists', ['uri' => $uri]);
$uri = $fetched_uri; $uri = $fetched_uri;
} }
foreach ($thread->replies as $reply) { foreach ($thread->replies ?? [] as $reply) {
bluesky_process_thread($reply, $uid, $cdata); $reply_uri = bluesky_process_thread($reply, $uid, $cdata, $level);
Logger::debug('Reply has been processed', ['uri' => $uri, 'reply' => $reply_uri]);
} }
return $uri; return $uri;
@ -1347,7 +1387,7 @@ function bluesky_get_contact_fields(stdClass $author, int $uid, bool $update): a
return $fields; return $fields;
} }
$data = bluesky_get($uid, '/xrpc/app.bsky.actor.getProfile?actor=' . $author->did, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]); $data = bluesky_xrpc_get($uid, 'app.bsky.actor.getProfile', ['actor' => $author->did]);
if (empty($data)) { if (empty($data)) {
Logger::debug('Error fetching contact fields', ['uid' => $uid, 'url' => $fields['url']]); Logger::debug('Error fetching contact fields', ['uid' => $uid, 'url' => $fields['url']]);
return $fields; return $fields;
@ -1399,7 +1439,7 @@ function bluesky_get_preferences(int $uid): stdClass
return $data; return $data;
} }
$data = bluesky_get($uid, '/xrpc/app.bsky.actor.getPreferences', HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]); $data = bluesky_xrpc_get($uid, 'app.bsky.actor.getPreferences');
DI::cache()->set($cachekey, $data, Duration::HOUR); DI::cache()->set($cachekey, $data, Duration::HOUR);
return $data; return $data;
@ -1407,7 +1447,7 @@ function bluesky_get_preferences(int $uid): stdClass
function bluesky_get_did(int $uid, string $handle): string function bluesky_get_did(int $uid, string $handle): string
{ {
$data = bluesky_get($uid, '/xrpc/com.atproto.identity.resolveHandle?handle=' . $handle); $data = bluesky_get($uid, '/xrpc/com.atproto.identity.resolveHandle?handle=' . urlencode($handle));
if (empty($data)) { if (empty($data)) {
return ''; return '';
} }
@ -1461,6 +1501,11 @@ function bluesky_create_token(int $uid, string $password): string
return $data->accessJwt; return $data->accessJwt;
} }
function bluesky_xrpc_post(int $uid, string $url, $parameters): ?stdClass
{
return bluesky_post($uid, '/xrpc/' . $url, json_encode($parameters), ['Content-type' => 'application/json', 'Authorization' => ['Bearer ' . bluesky_get_token($uid)]]);
}
function bluesky_post(int $uid, string $url, string $params, array $headers): ?stdClass function bluesky_post(int $uid, string $url, string $params, array $headers): ?stdClass
{ {
try { try {
@ -1478,6 +1523,15 @@ function bluesky_post(int $uid, string $url, string $params, array $headers): ?s
return json_decode($curlResult->getBody()); return json_decode($curlResult->getBody());
} }
function bluesky_xrpc_get(int $uid, string $url, array $parameters = []): ?stdClass
{
if (!empty($parameters)) {
$url .= '?' . http_build_query($parameters);
}
return bluesky_get($uid, '/xrpc/' . $url, HttpClientAccept::JSON, [HttpClientOptions::HEADERS => ['Authorization' => ['Bearer ' . bluesky_get_token($uid)]]]);
}
function bluesky_get(int $uid, string $url, string $accept_content = HttpClientAccept::DEFAULT, array $opts = []): ?stdClass function bluesky_get(int $uid, string $url, string $accept_content = HttpClientAccept::DEFAULT, array $opts = []): ?stdClass
{ {
try { try {

16
bluesky/bluesky_feed.php Normal file
View file

@ -0,0 +1,16 @@
<?php
use Friendica\Core\Logger;
function bluesky_feed_run($argv, $argc)
{
require_once 'addon/bluesky/bluesky.php';
if ($argc != 3) {
return;
}
Logger::debug('Importing feed - start', ['user' => $argv[1], 'feed' => $argv[2]]);
bluesky_fetch_feed($argv[1], $argv[2]);
Logger::debug('Importing feed - done', ['user' => $argv[1], 'feed' => $argv[2]]);
}

View file

@ -0,0 +1,16 @@
<?php
use Friendica\Core\Logger;
function bluesky_notifications_run($argv, $argc)
{
require_once 'addon/bluesky/bluesky.php';
if ($argc != 2) {
return;
}
Logger::notice('importing notifications - start', ['user' => $argv[1]]);
bluesky_fetch_notifications($argv[1]);
Logger::notice('importing notifications - done', ['user' => $argv[1]]);
}

View file

@ -0,0 +1,16 @@
<?php
use Friendica\Core\Logger;
function bluesky_timeline_run($argv, $argc)
{
require_once 'addon/bluesky/bluesky.php';
if ($argc != 2) {
return;
}
Logger::notice('importing timeline - start', ['user' => $argv[1]]);
bluesky_fetch_timeline($argv[1]);
Logger::notice('importing timeline - done', ['user' => $argv[1]]);
}