Skip to content

Commit

Permalink
Handle sync legacy labels (#2291)
Browse files Browse the repository at this point in the history
* Handle sync legacy labels

* Remap values on read

* Filter out double-written legacy label values

* Better naming, fix types
  • Loading branch information
estrattonbailey authored and pfrazee committed Mar 12, 2024
1 parent 2408225 commit 92a5be6
Show file tree
Hide file tree
Showing 2 changed files with 204 additions and 1 deletion.
75 changes: 75 additions & 0 deletions packages/api/src/bsky-agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,10 @@ export class BskyAgent extends AtpAgent {
}
}

prefs.moderationPrefs.labels = remapLegacyLabels(
prefs.moderationPrefs.labels,
)

// automatically configure the client
this.configureLabelersHeader(prefsArrayToLabelerDids(res.data.preferences))

Expand Down Expand Up @@ -510,6 +514,8 @@ export class BskyAgent extends AtpAgent {
pref.label === key &&
pref.labelerDid === labelerDid,
)
let legacyLabelPref: AppBskyActorDefs.ContentLabelPref | undefined

if (labelPref) {
labelPref.visibility = value
} else {
Expand All @@ -520,13 +526,58 @@ export class BskyAgent extends AtpAgent {
visibility: value,
}
}

if (AppBskyActorDefs.isContentLabelPref(labelPref)) {
// is global
if (!labelPref.labelerDid) {
const legacyLabelValue = {
'graphic-media': 'gore',
porn: 'nsfw',
sexual: 'suggestive',
}[labelPref.label]

// if it's a legacy label, double-write the legacy label
if (legacyLabelValue) {
legacyLabelPref = prefs.findLast(
(pref) =>
AppBskyActorDefs.isContentLabelPref(pref) &&
AppBskyActorDefs.validateContentLabelPref(pref).success &&
pref.label === legacyLabelValue &&
pref.labelerDid === undefined,
) as AppBskyActorDefs.ContentLabelPref | undefined

if (legacyLabelPref) {
legacyLabelPref.visibility = value
} else {
legacyLabelPref = {
$type: 'app.bsky.actor.defs#contentLabelPref',
label: legacyLabelValue,
labelerDid: undefined,
visibility: value,
}
}
}
}
}

return prefs
.filter(
(pref) =>
!AppBskyActorDefs.isContentLabelPref(pref) ||
!(pref.label === key && pref.labelerDid === labelerDid),
)
.concat([labelPref])
.filter((pref) => {
if (!legacyLabelPref) return true
return (
!AppBskyActorDefs.isContentLabelPref(pref) ||
!(
pref.label === legacyLabelPref.label &&
pref.labelerDid === undefined
)
)
})
.concat(legacyLabelPref ? [legacyLabelPref] : [])
})
}

Expand Down Expand Up @@ -861,6 +912,30 @@ function adjustLegacyContentLabelPref(
return { ...pref, visibility }
}

/**
* Re-maps legacy labels to new labels on READ. Does not save these changes to
* the user's preferences.
*/
function remapLegacyLabels(
labels: BskyPreferences['moderationPrefs']['labels'],
) {
const _labels = { ...labels }
const legacyToNewMap: Record<string, string | undefined> = {
gore: 'graphic-media',
nsfw: 'porn',
suggestive: 'sexual',
}

for (const labelName in _labels) {
const newLabelName = legacyToNewMap[labelName]!
if (newLabelName) {
_labels[newLabelName] = _labels[labelName]
}
}

return _labels
}

/**
* A helper to get the currently enabled labelers from the full preferences array
*/
Expand Down
130 changes: 129 additions & 1 deletion packages/api/tests/moderation-prefs.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ describe('agent', () => {
interests: { tags: [] },
moderationPrefs: {
adultContentEnabled: false,
labels: { ...DEFAULT_LABEL_SETTINGS, porn: 'ignore' },
labels: { ...DEFAULT_LABEL_SETTINGS, porn: 'ignore', nsfw: 'ignore' },
labelers: [
{
did: 'did:plc:other',
Expand Down Expand Up @@ -204,4 +204,132 @@ describe('agent', () => {
},
})
})

it(`updates label pref`, async () => {
const agent = new BskyAgent({ service: network.pds.url })

await agent.createAccount({
handle: 'user8.test',
email: '[email protected]',
password: 'password',
})

await agent.addLabeler('did:plc:other')
await agent.setContentLabelPref('porn', 'ignore')
await agent.setContentLabelPref('porn', 'ignore', 'did:plc:other')
await agent.setContentLabelPref('porn', 'hide')
await agent.setContentLabelPref('porn', 'hide', 'did:plc:other')

const { moderationPrefs } = await agent.getPreferences()
const labeler = moderationPrefs.labelers.find(
(l) => l.did === 'did:plc:other',
)

expect(moderationPrefs.labels.porn).toEqual('hide')
expect(labeler?.labels?.porn).toEqual('hide')
})

it(`double-write for legacy: 'graphic-media' in sync with 'gore'`, async () => {
const agent = new BskyAgent({ service: network.pds.url })

await agent.createAccount({
handle: 'user9.test',
email: '[email protected]',
password: 'password',
})

await agent.setContentLabelPref('graphic-media', 'hide')
const a = await agent.getPreferences()

expect(a.moderationPrefs.labels.gore).toEqual('hide')
expect(a.moderationPrefs.labels['graphic-media']).toEqual('hide')

await agent.setContentLabelPref('graphic-media', 'warn')
const b = await agent.getPreferences()

expect(b.moderationPrefs.labels.gore).toEqual('warn')
expect(b.moderationPrefs.labels['graphic-media']).toEqual('warn')
})

it(`double-write for legacy: 'porn' in sync with 'nsfw'`, async () => {
const agent = new BskyAgent({ service: network.pds.url })

await agent.createAccount({
handle: 'user10.test',
email: '[email protected]',
password: 'password',
})

await agent.setContentLabelPref('porn', 'hide')
const a = await agent.getPreferences()

expect(a.moderationPrefs.labels.nsfw).toEqual('hide')
expect(a.moderationPrefs.labels.porn).toEqual('hide')

await agent.setContentLabelPref('porn', 'warn')
const b = await agent.getPreferences()

expect(b.moderationPrefs.labels.nsfw).toEqual('warn')
expect(b.moderationPrefs.labels.porn).toEqual('warn')
})

it(`double-write for legacy: 'sexual' in sync with 'suggestive'`, async () => {
const agent = new BskyAgent({ service: network.pds.url })

await agent.createAccount({
handle: 'user11.test',
email: '[email protected]',
password: 'password',
})

await agent.setContentLabelPref('sexual', 'hide')
const a = await agent.getPreferences()

expect(a.moderationPrefs.labels.sexual).toEqual('hide')
expect(a.moderationPrefs.labels.suggestive).toEqual('hide')

await agent.setContentLabelPref('sexual', 'warn')
const b = await agent.getPreferences()

expect(b.moderationPrefs.labels.sexual).toEqual('warn')
expect(b.moderationPrefs.labels.suggestive).toEqual('warn')
})

it(`double-write for legacy: filters out existing old label pref if double-written`, async () => {
const agent = new BskyAgent({ service: network.pds.url })

await agent.createAccount({
handle: 'user12.test',
email: '[email protected]',
password: 'password',
})

await agent.setContentLabelPref('nsfw', 'hide')
await agent.setContentLabelPref('porn', 'hide')
const a = await agent.app.bsky.actor.getPreferences({})

const nsfwSettings = a.data.preferences.filter(
(pref) => pref.label === 'nsfw',
)
expect(nsfwSettings.length).toEqual(1)
})

it(`remaps old values to new on read`, async () => {
const agent = new BskyAgent({ service: network.pds.url })

await agent.createAccount({
handle: 'user13.test',
email: '[email protected]',
password: 'password',
})

await agent.setContentLabelPref('nsfw', 'hide')
await agent.setContentLabelPref('gore', 'hide')
await agent.setContentLabelPref('suggestive', 'hide')
const a = await agent.getPreferences()

expect(a.moderationPrefs.labels.porn).toEqual('hide')
expect(a.moderationPrefs.labels['graphic-media']).toEqual('hide')
expect(a.moderationPrefs.labels['sexual']).toEqual('hide')
})
})

0 comments on commit 92a5be6

Please sign in to comment.