UNPKG

@atproto/api

Version:

Client library for atproto and Bluesky

1,799 lines (1,741 loc) 104 kB
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: