@atproto/api
Version:
Client library for atproto and Bluesky
1,799 lines (1,741 loc) • 104 kB
text/typescript
import { TID } from '@atproto/common-web'
import { TestNetworkNoAppView } from '@atproto/dev-env'
import {
AppBskyActorDefs,
AppBskyActorProfile,
AtpAgent,
ComAtprotoRepoPutRecord,
DEFAULT_LABEL_SETTINGS,
} from '../src'
import { asPredicate } from '../src/client/util'
import {
getSavedFeedType,
savedFeedsToUriArrays,
validateSavedFeed,
} from '../src/util'
describe('agent', () => {
let network: TestNetworkNoAppView
beforeAll(async () => {
network = await TestNetworkNoAppView.create({
dbPostgresSchema: 'api_atp_agent',
})
})
afterAll(async () => {
await network.close()
})
const getProfileDisplayName = async (
agent: AtpAgent,
): Promise<string | undefined> => {
try {
const res = await agent.app.bsky.actor.profile.get({
repo: agent.accountDid,
rkey: 'self',
})
return res.value.displayName ?? ''
} catch (err) {
return undefined
}
}
it('clones correctly', () => {
const agent = new AtpAgent({ service: network.pds.url })
const agent2 = agent.clone()
expect(agent2 instanceof AtpAgent).toBeTruthy()
expect(agent.service).toEqual(agent2.service)
})
it('upsertProfile correctly creates and updates profiles.', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await agent.createAccount({
handle: 'user1.test',
email: 'user1@test.com',
password: 'password',
})
const displayName1 = await getProfileDisplayName(agent)
expect(displayName1).toBeFalsy()
await agent.upsertProfile((existing) => {
expect(existing).toBeFalsy()
return {
displayName: 'Bob',
}
})
const displayName2 = await getProfileDisplayName(agent)
expect(displayName2).toBe('Bob')
await agent.upsertProfile((existing) => {
expect(existing).toBeTruthy()
return {
displayName: existing?.displayName?.toUpperCase(),
}
})
const displayName3 = await getProfileDisplayName(agent)
expect(displayName3).toBe('BOB')
})
it('upsertProfile correctly handles CAS failures.', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await agent.createAccount({
handle: 'user2.test',
email: 'user2@test.com',
password: 'password',
})
const displayName1 = await getProfileDisplayName(agent)
expect(displayName1).toBeFalsy()
let hasConflicted = false
let ranTwice = false
await agent.upsertProfile(async (_existing) => {
if (!hasConflicted) {
await agent.com.atproto.repo.putRecord({
repo: agent.accountDid,
collection: 'app.bsky.actor.profile',
rkey: 'self',
record: {
$type: 'app.bsky.actor.profile',
displayName: String(Math.random()),
},
})
hasConflicted = true
} else {
ranTwice = true
}
return {
displayName: 'Bob',
}
})
expect(ranTwice).toBe(true)
const displayName2 = await getProfileDisplayName(agent)
expect(displayName2).toBe('Bob')
})
it('upsertProfile wont endlessly retry CAS failures.', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await agent.createAccount({
handle: 'user3.test',
email: 'user3@test.com',
password: 'password',
})
const displayName1 = await getProfileDisplayName(agent)
expect(displayName1).toBeFalsy()
const p = agent.upsertProfile(async (_existing) => {
await agent.com.atproto.repo.putRecord({
repo: agent.accountDid,
collection: 'app.bsky.actor.profile',
rkey: 'self',
record: {
$type: 'app.bsky.actor.profile',
displayName: String(Math.random()),
},
})
return {
displayName: 'Bob',
}
})
await expect(p).rejects.toThrow(ComAtprotoRepoPutRecord.InvalidSwapError)
})
it('upsertProfile validates the record.', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await agent.createAccount({
handle: 'user4.test',
email: 'user4@test.com',
password: 'password',
})
const p = agent.upsertProfile((_existing) => {
return {
displayName: { string: 'Bob' },
} as unknown as AppBskyActorProfile.Record
})
await expect(p).rejects.toThrow('Record/displayName must be a string')
})
describe('app', () => {
it('should retrieve the api app', () => {
const agent = new AtpAgent({ service: network.pds.url })
expect(agent.api).toBe(agent)
expect(agent.app).toBeDefined()
})
})
describe('post', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.post({ text: 'foo' })).rejects.toThrow('Not logged in')
})
})
describe('deletePost', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.deletePost('foo')).rejects.toThrow('Not logged in')
})
})
describe('like', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.like('foo', 'bar')).rejects.toThrow('Not logged in')
})
})
describe('deleteLike', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.deleteLike('foo')).rejects.toThrow('Not logged in')
})
})
describe('repost', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.repost('foo', 'bar')).rejects.toThrow('Not logged in')
})
})
describe('deleteRepost', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.deleteRepost('foo')).rejects.toThrow('Not logged in')
})
})
describe('follow', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.follow('foo')).rejects.toThrow('Not logged in')
})
})
describe('deleteFollow', () => {
it('should throw if no session', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await expect(agent.deleteFollow('foo')).rejects.toThrow('Not logged in')
})
})
describe('preferences methods', () => {
it('gets and sets preferences correctly', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await agent.createAccount({
handle: 'user5.test',
email: 'user5@test.com',
password: 'password',
})
const DEFAULT_LABELERS = AtpAgent.appLabelers.map((did) => ({
did,
labels: {},
}))
await expect(agent.getPreferences()).resolves.toStrictEqual({
feeds: { pinned: undefined, saved: undefined },
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
moderationPrefs: {
adultContentEnabled: false,
labels: DEFAULT_LABEL_SETTINGS,
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setAdultContentEnabled(true)
await expect(agent.getPreferences()).resolves.toStrictEqual({
feeds: { pinned: undefined, saved: undefined },
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
moderationPrefs: {
adultContentEnabled: true,
labels: DEFAULT_LABEL_SETTINGS,
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setAdultContentEnabled(false)
await expect(agent.getPreferences()).resolves.toStrictEqual({
feeds: { pinned: undefined, saved: undefined },
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
moderationPrefs: {
adultContentEnabled: false,
labels: DEFAULT_LABEL_SETTINGS,
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setContentLabelPref('misinfo', 'hide')
await expect(agent.getPreferences()).resolves.toStrictEqual({
feeds: { pinned: undefined, saved: undefined },
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
moderationPrefs: {
adultContentEnabled: false,
labels: { ...DEFAULT_LABEL_SETTINGS, misinfo: 'hide' },
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setContentLabelPref('spam', 'ignore')
await expect(agent.getPreferences()).resolves.toStrictEqual({
feeds: { pinned: undefined, saved: undefined },
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.addSavedFeed('at://bob.com/app.bsky.feed.generator/fake')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: [],
saved: ['at://bob.com/app.bsky.feed.generator/fake'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake'],
saved: ['at://bob.com/app.bsky.feed.generator/fake'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.removePinnedFeed('at://bob.com/app.bsky.feed.generator/fake')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: [],
saved: ['at://bob.com/app.bsky.feed.generator/fake'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.removeSavedFeed('at://bob.com/app.bsky.feed.generator/fake')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: [],
saved: [],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake'],
saved: ['at://bob.com/app.bsky.feed.generator/fake'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake2')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: [
'at://bob.com/app.bsky.feed.generator/fake',
'at://bob.com/app.bsky.feed.generator/fake2',
],
saved: [
'at://bob.com/app.bsky.feed.generator/fake',
'at://bob.com/app.bsky.feed.generator/fake2',
],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.removeSavedFeed('at://bob.com/app.bsky.feed.generator/fake')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: undefined,
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setPersonalDetails({ birthDate: '2023-09-11T18:05:42.556Z' })
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setFeedViewPrefs('home', { hideReplies: true })
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: true,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setFeedViewPrefs('home', { hideReplies: false })
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setFeedViewPrefs('other', { hideReplies: true })
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
other: {
hideReplies: true,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'hotness',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setThreadViewPrefs({ sort: 'random' })
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
other: {
hideReplies: true,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'random',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setThreadViewPrefs({ sort: 'oldest' })
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
other: {
hideReplies: true,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'oldest',
prioritizeFollowedUsers: true,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setInterestsPref({ tags: ['foo', 'bar'] })
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake2'],
saved: ['at://bob.com/app.bsky.feed.generator/fake2'],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
misinfo: 'hide',
spam: 'ignore',
},
labelers: DEFAULT_LABELERS,
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
other: {
hideReplies: true,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
},
threadViewPrefs: {
sort: 'oldest',
prioritizeFollowedUsers: true,
},
interests: {
tags: ['foo', 'bar'],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: [],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
})
it('resolves duplicates correctly', async () => {
const agent = new AtpAgent({ service: network.pds.url })
await agent.createAccount({
handle: 'user6.test',
email: 'user6@test.com',
password: 'password',
})
await agent.app.bsky.actor.putPreferences({
preferences: [
{
$type: 'app.bsky.actor.defs#contentLabelPref',
label: 'porn',
visibility: 'show',
},
{
$type: 'app.bsky.actor.defs#contentLabelPref',
label: 'porn',
visibility: 'hide',
},
{
$type: 'app.bsky.actor.defs#contentLabelPref',
label: 'porn',
visibility: 'show',
},
{
$type: 'app.bsky.actor.defs#contentLabelPref',
label: 'porn',
visibility: 'warn',
},
{
$type: 'app.bsky.actor.defs#labelersPref',
labelers: [
{
did: 'did:plc:first-labeler',
},
],
},
{
$type: 'app.bsky.actor.defs#labelersPref',
labelers: [
{
did: 'did:plc:first-labeler',
},
{
did: 'did:plc:other',
},
],
},
{
$type: 'app.bsky.actor.defs#adultContentPref',
enabled: true,
},
{
$type: 'app.bsky.actor.defs#adultContentPref',
enabled: false,
},
{
$type: 'app.bsky.actor.defs#adultContentPref',
enabled: true,
},
{
$type: 'app.bsky.actor.defs#savedFeedsPref',
pinned: [
'at://bob.com/app.bsky.feed.generator/fake',
'at://bob.com/app.bsky.feed.generator/fake2',
],
saved: [
'at://bob.com/app.bsky.feed.generator/fake',
'at://bob.com/app.bsky.feed.generator/fake2',
],
},
{
$type: 'app.bsky.actor.defs#savedFeedsPref',
pinned: [],
saved: [],
},
{
$type: 'app.bsky.actor.defs#personalDetailsPref',
birthDate: '2023-09-11T18:05:42.556Z',
},
{
$type: 'app.bsky.actor.defs#personalDetailsPref',
birthDate: '2021-09-11T18:05:42.556Z',
},
{
$type: 'app.bsky.actor.defs#feedViewPref',
feed: 'home',
hideReplies: false,
hideRepliesByUnfollowed: true,
hideRepliesByLikeCount: 0,
hideReposts: false,
hideQuotePosts: false,
},
{
$type: 'app.bsky.actor.defs#feedViewPref',
feed: 'home',
hideReplies: true,
hideRepliesByUnfollowed: false,
hideRepliesByLikeCount: 10,
hideReposts: true,
hideQuotePosts: true,
},
{
$type: 'app.bsky.actor.defs#threadViewPref',
sort: 'oldest',
prioritizeFollowedUsers: true,
},
{
$type: 'app.bsky.actor.defs#threadViewPref',
sort: 'newest',
prioritizeFollowedUsers: false,
},
{
$type: 'app.bsky.actor.defs#bskyAppStatePref',
queuedNudges: ['one'],
},
{
$type: 'app.bsky.actor.defs#bskyAppStatePref',
activeProgressGuide: undefined,
queuedNudges: ['two'],
},
],
})
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
type: 'timeline',
value: 'following',
pinned: true,
},
],
feeds: {
pinned: [],
saved: [],
},
moderationPrefs: {
adultContentEnabled: true,
labels: {
...DEFAULT_LABEL_SETTINGS,
porn: 'warn',
},
labelers: [
...AtpAgent.appLabelers.map((did) => ({ did, labels: {} })),
{
did: 'did:plc:first-labeler',
labels: {},
},
{
did: 'did:plc:other',
labels: {},
},
],
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2021-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: true,
hideRepliesByUnfollowed: false,
hideRepliesByLikeCount: 10,
hideReposts: true,
hideQuotePosts: true,
},
},
threadViewPrefs: {
sort: 'newest',
prioritizeFollowedUsers: false,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: ['two'],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setAdultContentEnabled(false)
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
type: 'timeline',
value: 'following',
pinned: true,
},
],
feeds: {
pinned: [],
saved: [],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
porn: 'warn',
},
labelers: [
...AtpAgent.appLabelers.map((did) => ({ did, labels: {} })),
{
did: 'did:plc:first-labeler',
labels: {},
},
{
did: 'did:plc:other',
labels: {},
},
],
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2021-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: true,
hideRepliesByUnfollowed: false,
hideRepliesByLikeCount: 10,
hideReposts: true,
hideQuotePosts: true,
},
},
threadViewPrefs: {
sort: 'newest',
prioritizeFollowedUsers: false,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: ['two'],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setContentLabelPref('porn', 'ignore')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
type: 'timeline',
value: 'following',
pinned: true,
},
],
feeds: {
pinned: [],
saved: [],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
nsfw: 'ignore',
porn: 'ignore',
},
labelers: [
...AtpAgent.appLabelers.map((did) => ({ did, labels: {} })),
{
did: 'did:plc:first-labeler',
labels: {},
},
{
did: 'did:plc:other',
labels: {},
},
],
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2021-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: true,
hideRepliesByUnfollowed: false,
hideRepliesByLikeCount: 10,
hideReposts: true,
hideQuotePosts: true,
},
},
threadViewPrefs: {
sort: 'newest',
prioritizeFollowedUsers: false,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: ['two'],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.removeLabeler('did:plc:other')
await expect(agent.getPreferences()).resolves.toStrictEqual({
savedFeeds: [
{
id: expect.any(String),
type: 'timeline',
value: 'following',
pinned: true,
},
],
feeds: {
pinned: [],
saved: [],
},
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
nsfw: 'ignore',
porn: 'ignore',
},
labelers: [
...AtpAgent.appLabelers.map((did) => ({ did, labels: {} })),
{
did: 'did:plc:first-labeler',
labels: {},
},
],
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2021-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: true,
hideRepliesByUnfollowed: false,
hideRepliesByLikeCount: 10,
hideReposts: true,
hideQuotePosts: true,
},
},
threadViewPrefs: {
sort: 'newest',
prioritizeFollowedUsers: false,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: ['two'],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.addPinnedFeed('at://bob.com/app.bsky.feed.generator/fake')
await expect(agent.getPreferences()).resolves.toStrictEqual({
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake'],
saved: ['at://bob.com/app.bsky.feed.generator/fake'],
},
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
nsfw: 'ignore',
porn: 'ignore',
},
labelers: [
...AtpAgent.appLabelers.map((did) => ({ did, labels: {} })),
{
did: 'did:plc:first-labeler',
labels: {},
},
],
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2021-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: true,
hideRepliesByUnfollowed: false,
hideRepliesByLikeCount: 10,
hideReposts: true,
hideQuotePosts: true,
},
},
threadViewPrefs: {
sort: 'newest',
prioritizeFollowedUsers: false,
},
interests: {
tags: [],
},
bskyAppState: {
activeProgressGuide: undefined,
queuedNudges: ['two'],
nuxs: [],
},
postInteractionSettings: {
threadgateAllowRules: undefined,
postgateEmbeddingRules: undefined,
},
verificationPrefs: {
hideBadges: false,
},
})
await agent.setPersonalDetails({ birthDate: '2023-09-11T18:05:42.556Z' })
await expect(agent.getPreferences()).resolves.toStrictEqual({
feeds: {
pinned: ['at://bob.com/app.bsky.feed.generator/fake'],
saved: ['at://bob.com/app.bsky.feed.generator/fake'],
},
savedFeeds: [
{
id: expect.any(String),
pinned: true,
type: 'timeline',
value: 'following',
},
],
moderationPrefs: {
adultContentEnabled: false,
labels: {
...DEFAULT_LABEL_SETTINGS,
nsfw: 'ignore',
porn: 'ignore',
},
labelers: [
...AtpAgent.appLabelers.map((did) => ({ did, labels: {} })),
{
did: 'did:plc:first-labeler',
labels: {},
},
],
mutedWords: [],
hiddenPosts: [],
},
birthDate: new Date('2023-09-11T18:05:42.556Z'),
feedViewPrefs: {
home: {
hideReplies: true,
hideRepliesByUnfollowed: false,
hideRepliesByLikeCount: 10,
hideReposts: true,
hideQuotePosts: true,
},
},
threadViewPrefs: {
sort: 'newest',
prioritizeFollowedUsers: false,
},
interests: