filter hasPart recent posts on hide labels
Mirrors the gating applied to comment[]: recent posts whose own labels match hideEmbedLabels or hideReplyLabels are dropped from hasPart entirely so flagged content isn't surfaced into the profile's structured data. Negated post-view labels are honored.
This commit is contained in:
@@ -411,7 +411,9 @@ func buildPostJSONLD(pv *appbsky.FeedDefs_PostView, replies []*appbsky.FeedDefs_
|
||||
}
|
||||
|
||||
// buildProfileJSONLD marshals a ProfilePage (with hasPart recent posts).
|
||||
func buildProfileJSONLD(pv *appbsky.ActorDefs_ProfileViewDetailed, recentPosts []*appbsky.FeedDefs_PostView, hideLabels map[string]bool) (string, error) {
|
||||
// Recent posts whose own labels match hideLabels or hideReplyLabels are
|
||||
// dropped from hasPart, mirroring the gating applied to comment[].
|
||||
func buildProfileJSONLD(pv *appbsky.ActorDefs_ProfileViewDetailed, recentPosts []*appbsky.FeedDefs_PostView, hideLabels, hideReplyLabels map[string]bool) (string, error) {
|
||||
if pv == nil {
|
||||
return "", fmt.Errorf("nil profile view")
|
||||
}
|
||||
@@ -463,6 +465,11 @@ func buildProfileJSONLD(pv *appbsky.ActorDefs_ProfileViewDetailed, recentPosts [
|
||||
if len(page.HasPart) >= maxRecentPosts {
|
||||
break
|
||||
}
|
||||
// Drop labeled posts entirely so flagged content isn't surfaced
|
||||
// into the profile's structured data.
|
||||
if postHasHideLabel(rp, hideReplyLabels) || postHasHideLabel(rp, hideLabels) {
|
||||
continue
|
||||
}
|
||||
// Recent posts go in nested form. No replies are passed, so the
|
||||
// reply-label set is irrelevant; pass nil.
|
||||
node := buildPostNode(rp, nil, hideLabels, nil)
|
||||
|
||||
@@ -530,7 +530,7 @@ func TestBuildProfileJSONLD_Basic(t *testing.T) {
|
||||
PostsCount: intPtr(200),
|
||||
CreatedAt: strPtr("2023-01-01T00:00:00Z"),
|
||||
}
|
||||
out, err := buildProfileJSONLD(pv, nil, hideEmbedLabels)
|
||||
out, err := buildProfileJSONLD(pv, nil, hideEmbedLabels, hideReplyLabels)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -570,7 +570,7 @@ func TestBuildProfileJSONLD_HasPart(t *testing.T) {
|
||||
posts = append(posts, makePostView("alice.bsky.social", "did:plc:alice",
|
||||
"r"+string(rune('0'+i)), "post"))
|
||||
}
|
||||
out, _ := buildProfileJSONLD(pv, posts, hideEmbedLabels)
|
||||
out, _ := buildProfileJSONLD(pv, posts, hideEmbedLabels, hideReplyLabels)
|
||||
page := unmarshalLD(t, out)
|
||||
hp, ok := page["hasPart"].([]any)
|
||||
if !ok {
|
||||
@@ -588,6 +588,51 @@ func TestBuildProfileJSONLD_HasPart(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildProfileJSONLD_HasPartLabelFiltered(t *testing.T) {
|
||||
// Recent posts carrying hideReplyLabels or hideEmbedLabels are dropped
|
||||
// from hasPart entirely. Negation is honored on post-view labels.
|
||||
pv := &appbsky.ActorDefs_ProfileViewDetailed{
|
||||
Did: "did:plc:alice", Handle: "alice.bsky.social",
|
||||
DisplayName: strPtr("Alice"),
|
||||
CreatedAt: strPtr("2023-01-01T00:00:00Z"),
|
||||
}
|
||||
good := makePostView("alice.bsky.social", "did:plc:alice", "good", "ok")
|
||||
hidden := makePostView("alice.bsky.social", "did:plc:alice", "hide", "hidden",
|
||||
withPostLabel("!hide", false))
|
||||
spam := makePostView("alice.bsky.social", "did:plc:alice", "spam", "spam",
|
||||
withSelfLabel("spam"))
|
||||
embedHide := makePostView("alice.bsky.social", "did:plc:alice", "harm", "embed-only",
|
||||
withPostLabel("self-harm", false))
|
||||
negated := makePostView("alice.bsky.social", "did:plc:alice", "neg", "negated",
|
||||
withPostLabel("!hide", true))
|
||||
|
||||
out, _ := buildProfileJSONLD(pv, []*appbsky.FeedDefs_PostView{
|
||||
good, hidden, spam, embedHide, negated,
|
||||
}, hideEmbedLabels, hideReplyLabels)
|
||||
page := unmarshalLD(t, out)
|
||||
hp, _ := page["hasPart"].([]any)
|
||||
|
||||
got := make(map[string]bool, len(hp))
|
||||
for _, e := range hp {
|
||||
got[e.(map[string]any)["identifier"].(string)] = true
|
||||
}
|
||||
if !got[good.Uri] {
|
||||
t.Errorf("expected unlabeled post in hasPart")
|
||||
}
|
||||
if !got[negated.Uri] {
|
||||
t.Errorf("negated hide label should not gate; expected post in hasPart")
|
||||
}
|
||||
if got[hidden.Uri] {
|
||||
t.Errorf("post with !hide label should be dropped from hasPart")
|
||||
}
|
||||
if got[spam.Uri] {
|
||||
t.Errorf("self-labeled spam post should be dropped from hasPart")
|
||||
}
|
||||
if got[embedHide.Uri] {
|
||||
t.Errorf("post with hideEmbedLabels label should be dropped from hasPart")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBskyPostURL(t *testing.T) {
|
||||
tests := []struct {
|
||||
name, handle, rkey, want string
|
||||
|
||||
@@ -114,7 +114,7 @@ func TestRenderPost_FallsBackToCanonicalizeFilter(t *testing.T) {
|
||||
|
||||
func TestRenderProfile_EmitsJSONLD(t *testing.T) {
|
||||
pv := newProfileViewDetailed()
|
||||
ld, err := buildProfileJSONLD(pv, nil, hideEmbedLabels)
|
||||
ld, err := buildProfileJSONLD(pv, nil, hideEmbedLabels, hideReplyLabels)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -140,7 +140,7 @@ func TestRenderProfile_EmitsJSONLD(t *testing.T) {
|
||||
// before buildProfileJSONLD.
|
||||
func TestRenderProfile_AuthRequiredEmitsJSONLD(t *testing.T) {
|
||||
pv := newProfileViewDetailed()
|
||||
ld, err := buildProfileJSONLD(pv, nil, hideEmbedLabels)
|
||||
ld, err := buildProfileJSONLD(pv, nil, hideEmbedLabels, hideReplyLabels)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
@@ -713,7 +713,7 @@ func (srv *Server) WebProfile(c echo.Context) error {
|
||||
data["requiresAuth"] = true
|
||||
}
|
||||
|
||||
if jsonld, err := buildProfileJSONLD(pv, recentPosts, hideEmbedLabels); err == nil {
|
||||
if jsonld, err := buildProfileJSONLD(pv, recentPosts, hideEmbedLabels, hideReplyLabels); err == nil {
|
||||
data["profileJSONLD"] = jsonld
|
||||
} else {
|
||||
log.Warnf("failed to build profile JSON-LD for %s: %v", pv.Did, err)
|
||||
|
||||
Reference in New Issue
Block a user