perlsky is a Perl 5 implementation of an AT Protocol Personal Data Server.
13
fork

Configure Feed

Select the types of activity you want to include in your feed.

Align local app.bsky views with lexicons

alice ccc2a1c6 0dddad8b

+142 -8
+131 -7
lib/ATProto/PDS/ServiceProxy.pm
··· 225 225 my $result = { 226 226 %{ $self->_profile_view_detailed($c, $account, $profile_value) }, 227 227 associated => { 228 + %{ $self->_profile_associated }, 228 229 lists => 0, 229 230 feedgens => 0, 230 231 starterPacks => 0, 231 232 labeler => JSON::PP::false, 232 - activitySubscription => { 233 - allowSubscriptions => 'followers', 234 - }, 235 233 }, 236 234 viewer => { 237 235 muted => JSON::PP::false, ··· 335 333 my $view = { 336 334 did => $account->{did}, 337 335 handle => $account->{handle}, 336 + associated => $self->_profile_associated, 337 + labels => [], 338 + createdAt => iso8601($account->{created_at}), 338 339 }; 339 - $view->{displayName} = $profile_value->{displayName} if defined $profile_value->{displayName}; 340 + $view->{displayName} = $profile_value->{displayName} 341 + if defined($profile_value->{displayName}) && length($profile_value->{displayName}); 342 + $view->{pronouns} = $profile_value->{pronouns} 343 + if defined($profile_value->{pronouns}) && length($profile_value->{pronouns}); 340 344 if (my $avatar_cid = $self->_blob_cid($profile_value->{avatar})) { 341 345 $view->{avatar} = $self->_blob_url($c, $account->{did}, $avatar_cid); 342 346 } ··· 347 351 $profile_value //= $self->_profile_record_value($c, $account); 348 352 my $view = { 349 353 %{ $self->_profile_view_basic($c, $account, $profile_value) }, 350 - labels => [], 351 354 createdAt => iso8601($account->{created_at}), 355 + indexedAt => iso8601($account->{created_at}), 352 356 followersCount => 0, 353 357 followsCount => 0, 354 358 postsCount => 0 + $c->store->count_records_by_collection($account->{did}, 'app.bsky.feed.post'), 355 359 }; 356 - $view->{description} = $profile_value->{description} if defined $profile_value->{description}; 360 + $view->{description} = $profile_value->{description} 361 + if defined($profile_value->{description}) && length($profile_value->{description}); 362 + $view->{website} = $profile_value->{website} 363 + if defined($profile_value->{website}) && length($profile_value->{website}); 357 364 if (my $banner_cid = $self->_blob_cid($profile_value->{banner})) { 358 365 $view->{banner} = $self->_blob_url($c, $account->{did}, $banner_cid); 359 366 } 360 367 return $view; 361 368 } 362 369 370 + sub _profile_associated ($self) { 371 + return { 372 + chat => { 373 + allowIncoming => 'all', 374 + }, 375 + activitySubscription => { 376 + allowSubscriptions => 'followers', 377 + }, 378 + }; 379 + } 380 + 363 381 sub _blob_cid ($self, $blob) { 364 382 return undef unless ref($blob) eq 'HASH'; 365 383 return $blob->{ref}{'$link'} if ref($blob->{ref}) eq 'HASH' && defined $blob->{ref}{'$link'}; ··· 393 411 return iso8601($row->{created_at} // $row->{updated_at}); 394 412 } 395 413 396 - sub _post_view ($self, $c, $account, $row, $profile_value = undef, $viewer = undef) { 414 + sub _post_view ($self, $c, $account, $row, $profile_value = undef, $viewer = undef, $depth = 0) { 397 415 my $uri = $self->_post_uri($account, $row); 398 416 my $counts = $self->_post_counts_and_viewer($c, $uri, $viewer); 399 417 my $post = { ··· 401 419 cid => $row->{cid}, 402 420 author => $self->_profile_view_basic($c, $account, $profile_value), 403 421 record => $row->{value}, 422 + bookmarkCount => 0, 404 423 replyCount => $counts->{replyCount}, 405 424 repostCount => $counts->{repostCount}, 406 425 likeCount => $counts->{likeCount}, ··· 408 427 indexedAt => $self->_post_indexed_at($row), 409 428 labels => [], 410 429 }; 430 + if ($depth < 2) { 431 + my $embed = $self->_post_embed_view($c, $account, $row->{value}, $viewer, $depth + 1); 432 + $post->{embed} = $embed if defined $embed; 433 + } 411 434 $post->{viewer} = $counts->{viewer} if %{ $counts->{viewer} }; 412 435 return $post; 413 436 } ··· 510 533 && ref($embed->{record}) eq 'HASH' 511 534 && ref($embed->{record}{record}) eq 'HASH'; 512 535 return undef; 536 + } 537 + 538 + sub _post_embed_view ($self, $c, $account, $value, $viewer = undef, $depth = 0) { 539 + return undef unless ref($value) eq 'HASH'; 540 + my $embed = $value->{embed}; 541 + return undef unless ref($embed) eq 'HASH'; 542 + 543 + my $type = $embed->{'$type'} // q(); 544 + if ($type eq 'app.bsky.embed.images') { 545 + my @images = map { 546 + my $cid = $self->_blob_cid($_->{image}); 547 + +{ 548 + thumb => $self->_blob_url($c, $account->{did}, $cid), 549 + fullsize => $self->_blob_url($c, $account->{did}, $cid), 550 + alt => $_->{alt} // q(), 551 + (ref($_->{aspectRatio}) eq 'HASH' ? (aspectRatio => $_->{aspectRatio}) : ()), 552 + } 553 + } grep { ref($_) eq 'HASH' && $self->_blob_cid($_->{image}) } @{ $embed->{images} // [] }; 554 + 555 + return undef unless @images; 556 + return { 557 + '$type' => 'app.bsky.embed.images#view', 558 + images => \@images, 559 + }; 560 + } 561 + 562 + if ($type eq 'app.bsky.embed.external' && ref($embed->{external}) eq 'HASH') { 563 + my $external = $embed->{external}; 564 + my %view = ( 565 + uri => $external->{uri} // q(), 566 + title => $external->{title} // q(), 567 + description => $external->{description} // q(), 568 + ); 569 + if (my $cid = $self->_blob_cid($external->{thumb})) { 570 + $view{thumb} = $self->_blob_url($c, $account->{did}, $cid); 571 + } 572 + return { 573 + '$type' => 'app.bsky.embed.external#view', 574 + external => \%view, 575 + }; 576 + } 577 + 578 + if ($type eq 'app.bsky.embed.record' && ref($embed->{record}) eq 'HASH') { 579 + return { 580 + '$type' => 'app.bsky.embed.record#view', 581 + record => $self->_record_embed_view($c, $embed->{record}, $viewer, $depth), 582 + }; 583 + } 584 + 585 + if ($type eq 'app.bsky.embed.recordWithMedia' 586 + && ref($embed->{record}) eq 'HASH' 587 + && ref($embed->{media}) eq 'HASH') { 588 + my $record = $self->_record_embed_view($c, $embed->{record}{record} // $embed->{record}, $viewer, $depth); 589 + my $media = $self->_post_embed_view( 590 + $c, 591 + $account, 592 + { embed => $embed->{media} }, 593 + $viewer, 594 + $depth, 595 + ); 596 + return undef unless defined $record && defined $media; 597 + return { 598 + '$type' => 'app.bsky.embed.recordWithMedia#view', 599 + record => { 600 + '$type' => 'app.bsky.embed.record#view', 601 + record => $record, 602 + }, 603 + media => $media, 604 + }; 605 + } 606 + 607 + return undef; 608 + } 609 + 610 + sub _record_embed_view ($self, $c, $record_ref, $viewer = undef, $depth = 0) { 611 + my $uri = $record_ref->{uri} // q(); 612 + my $resolved = $self->_resolve_local_post_uri($c, $uri); 613 + return { 614 + '$type' => 'app.bsky.embed.record#viewNotFound', 615 + uri => $uri, 616 + notFound => JSON::PP::true, 617 + } unless $resolved; 618 + 619 + my ($account, $row) = @$resolved; 620 + my $profile_value = $self->_profile_record_value($c, $account); 621 + my $post_view = $self->_post_view($c, $account, $row, $profile_value, $viewer, $depth); 622 + my %record_view = ( 623 + '$type' => 'app.bsky.embed.record#viewRecord', 624 + uri => $post_view->{uri}, 625 + cid => $post_view->{cid}, 626 + author => $post_view->{author}, 627 + value => $post_view->{record}, 628 + indexedAt => $post_view->{indexedAt}, 629 + ); 630 + $record_view{labels} = $post_view->{labels} if $post_view->{labels}; 631 + $record_view{replyCount} = $post_view->{replyCount} if defined $post_view->{replyCount}; 632 + $record_view{repostCount} = $post_view->{repostCount} if defined $post_view->{repostCount}; 633 + $record_view{likeCount} = $post_view->{likeCount} if defined $post_view->{likeCount}; 634 + $record_view{quoteCount} = $post_view->{quoteCount} if defined $post_view->{quoteCount}; 635 + $record_view{embeds} = [ $post_view->{embed} ] if defined $post_view->{embed}; 636 + return \%record_view; 513 637 } 514 638 515 639 1;
+11 -1
t/service-proxy.t
··· 150 150 })->status_is(200) 151 151 ->json_is('/did' => $did) 152 152 ->json_is('/handle' => $created->{handle}) 153 + ->json_is('/associated/chat/allowIncoming' => 'all') 154 + ->json_is('/associated/activitySubscription/allowSubscriptions' => 'followers') 155 + ->json_is('/labels' => []) 156 + ->json_has('/createdAt') 157 + ->json_has('/indexedAt') 153 158 ->json_is('/postsCount' => 0); 154 159 155 160 $t->post_ok('/xrpc/com.atproto.repo.createRecord' => { ··· 177 182 Authorization => "Bearer $access", 178 183 })->status_is(200) 179 184 ->json_is('/feed/0/post/uri' => $post_uri) 180 - ->json_is('/feed/0/post/record/text' => 'browser smoke post'); 185 + ->json_is('/feed/0/post/record/text' => 'browser smoke post') 186 + ->json_is('/feed/0/post/bookmarkCount' => 0) 187 + ->json_is('/feed/0/post/author/associated/chat/allowIncoming' => 'all') 188 + ->json_is('/feed/0/post/author/associated/activitySubscription/allowSubscriptions' => 'followers') 189 + ->json_is('/feed/0/post/author/labels' => []) 190 + ->json_has('/feed/0/post/author/createdAt'); 181 191 182 192 $t->get_ok('/xrpc/app.bsky.feed.getPostThread?uri=' . _uri_escape($post_uri) => { 183 193 Authorization => "Bearer $access",