UNPKG

@atproto/api

Version:

Client library for atproto and Bluesky

1,282 lines 54.7 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _Agent_prefsLock; Object.defineProperty(exports, "__esModule", { value: true }); exports.Agent = void 0; const await_lock_1 = __importDefault(require("await-lock")); const common_web_1 = require("@atproto/common-web"); const syntax_1 = require("@atproto/syntax"); const xrpc_1 = require("@atproto/xrpc"); const index_1 = require("./client/index"); const lexicons_1 = require("./client/lexicons"); const const_1 = require("./const"); const moderation_1 = require("./moderation"); const labels_1 = require("./moderation/const/labels"); const predicate = __importStar(require("./predicate")); const util_1 = require("./util"); const FEED_VIEW_PREF_DEFAULTS = { hideReplies: false, hideRepliesByUnfollowed: true, hideRepliesByLikeCount: 0, hideReposts: false, hideQuotePosts: false, }; const THREAD_VIEW_PREF_DEFAULTS = { sort: 'hotness', prioritizeFollowedUsers: true, }; /** * An {@link Agent} is an {@link AtpBaseClient} with the following * additional features: * - AT Protocol labelers configuration utilities * - AT Protocol proxy configuration utilities * - Cloning utilities * - `app.bsky` syntactic sugar * - `com.atproto` syntactic sugar */ class Agent extends xrpc_1.XrpcClient { /** * Configures the Agent (or its sub classes) globally. */ static configure(opts) { if (opts.appLabelers) { this.appLabelers = opts.appLabelers.map(util_1.asDid); // Validate & copy } } /** @deprecated use `this` instead */ get xrpc() { return this; } constructor(options) { const sessionManager = typeof options === 'object' && 'fetchHandler' in options ? options : { did: undefined, fetchHandler: (0, xrpc_1.buildFetchHandler)(options), }; super((url, init) => { const headers = new Headers(init?.headers); if (this.proxy && !headers.has('atproto-proxy')) { headers.set('atproto-proxy', this.proxy); } // Merge the labelers header of this particular request with the app & // instance labelers. headers.set('atproto-accept-labelers', [ ...this.appLabelers.map((l) => `${l};redact`), ...this.labelers, headers.get('atproto-accept-labelers')?.trim(), ] .filter(Boolean) .join(', ')); return this.sessionManager.fetchHandler(url, { ...init, headers }); }, lexicons_1.schemas); //#endregion Object.defineProperty(this, "com", { enumerable: true, configurable: true, writable: true, value: new index_1.ComNS(this) }); Object.defineProperty(this, "app", { enumerable: true, configurable: true, writable: true, value: new index_1.AppNS(this) }); Object.defineProperty(this, "chat", { enumerable: true, configurable: true, writable: true, value: new index_1.ChatNS(this) }); Object.defineProperty(this, "tools", { enumerable: true, configurable: true, writable: true, value: new index_1.ToolsNS(this) }); Object.defineProperty(this, "sessionManager", { enumerable: true, configurable: true, writable: true, value: void 0 }); Object.defineProperty(this, "labelers", { enumerable: true, configurable: true, writable: true, value: [] }); //#endregion //#region ATPROTO proxy configuration utilities Object.defineProperty(this, "proxy", { enumerable: true, configurable: true, writable: true, value: void 0 }); //#region "com.atproto" lexicon short hand methods /** * Upload a binary blob to the server */ Object.defineProperty(this, "uploadBlob", { enumerable: true, configurable: true, writable: true, value: (data, opts) => this.com.atproto.repo.uploadBlob(data, opts) }); /** * Resolve a handle to a DID */ Object.defineProperty(this, "resolveHandle", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.com.atproto.identity.resolveHandle(params, opts) }); /** * Change the user's handle */ Object.defineProperty(this, "updateHandle", { enumerable: true, configurable: true, writable: true, value: (data, opts) => this.com.atproto.identity.updateHandle(data, opts) }); /** * Create a moderation report */ Object.defineProperty(this, "createModerationReport", { enumerable: true, configurable: true, writable: true, value: (data, opts) => this.com.atproto.moderation.createReport(data, opts) }); //#endregion //#region "app.bsky" lexicon short hand methods Object.defineProperty(this, "getTimeline", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.feed.getTimeline(params, opts) }); Object.defineProperty(this, "getAuthorFeed", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.feed.getAuthorFeed(params, opts) }); Object.defineProperty(this, "getActorLikes", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.feed.getActorLikes(params, opts) }); Object.defineProperty(this, "getPostThread", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.feed.getPostThread(params, opts) }); Object.defineProperty(this, "getPost", { enumerable: true, configurable: true, writable: true, value: (params) => this.app.bsky.feed.post.get(params) }); Object.defineProperty(this, "getPosts", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.feed.getPosts(params, opts) }); Object.defineProperty(this, "getLikes", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.feed.getLikes(params, opts) }); Object.defineProperty(this, "getRepostedBy", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.feed.getRepostedBy(params, opts) }); Object.defineProperty(this, "getFollows", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.graph.getFollows(params, opts) }); Object.defineProperty(this, "getFollowers", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.graph.getFollowers(params, opts) }); Object.defineProperty(this, "getProfile", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.actor.getProfile(params, opts) }); Object.defineProperty(this, "getProfiles", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.actor.getProfiles(params, opts) }); Object.defineProperty(this, "getSuggestions", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.actor.getSuggestions(params, opts) }); Object.defineProperty(this, "searchActors", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.actor.searchActors(params, opts) }); Object.defineProperty(this, "searchActorsTypeahead", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.actor.searchActorsTypeahead(params, opts) }); Object.defineProperty(this, "listNotifications", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.notification.listNotifications(params, opts) }); Object.defineProperty(this, "countUnreadNotifications", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.notification.getUnreadCount(params, opts) }); Object.defineProperty(this, "getLabelers", { enumerable: true, configurable: true, writable: true, value: (params, opts) => this.app.bsky.labeler.getServices(params, opts) }); //- Private methods _Agent_prefsLock.set(this, new await_lock_1.default() /** * This function updates the preferences of a user and allows for a callback function to be executed * before the update. * @param cb - cb is a callback function that takes in a single parameter of type * AppBskyActorDefs.Preferences and returns either a boolean or void. This callback function is used to * update the preferences of the user. The function is called with the current preferences as an * argument and if the callback returns false, the preferences are not updated. */ ); this.sessionManager = sessionManager; } //#region Cloning utilities clone() { return this.copyInto(new Agent(this.sessionManager)); } copyInto(inst) { inst.configureLabelers(this.labelers); inst.configureProxy(this.proxy ?? null); inst.clearHeaders(); for (const [key, value] of this.headers) inst.setHeader(key, value); return inst; } withProxy(serviceType, did) { const inst = this.clone(); inst.configureProxy(`${(0, util_1.asDid)(did)}#${serviceType}`); return inst; } //#endregion //#region ATPROTO labelers configuration utilities /** * The labelers statically configured on the class of the current instance. */ get appLabelers() { return this.constructor.appLabelers; } configureLabelers(labelerDids) { this.labelers = labelerDids.map(util_1.asDid); // Validate & copy } /** @deprecated use {@link configureLabelers} instead */ configureLabelersHeader(labelerDids) { // Filtering non-did values for backwards compatibility this.configureLabelers(labelerDids.filter(util_1.isDid)); } configureProxy(value) { if (value === null) this.proxy = undefined; else if ((0, util_1.isDid)(value)) this.proxy = value; else throw new TypeError('Invalid proxy DID'); } /** @deprecated use {@link configureProxy} instead */ configureProxyHeader(serviceType, did) { // Ignoring non-did values for backwards compatibility if ((0, util_1.isDid)(did)) this.configureProxy(`${did}#${serviceType}`); } //#endregion //#region Session management /** * Get the authenticated user's DID, if any. */ get did() { return this.sessionManager.did; } /** @deprecated Use {@link Agent.assertDid} instead */ get accountDid() { return this.assertDid; } /** * Get the authenticated user's DID, or throw an error if not authenticated. */ get assertDid() { this.assertAuthenticated(); return this.did; } /** * Assert that the user is authenticated. */ assertAuthenticated() { if (!this.did) throw new Error('Not logged in'); } //#endregion /** @deprecated use "this" instead */ get api() { return this; } async getLabelDefinitions(prefs) { // collect the labeler dids const dids = [...this.appLabelers]; if (isBskyPrefs(prefs)) { dids.push(...prefs.moderationPrefs.labelers.map((l) => l.did)); } else if (isModPrefs(prefs)) { dids.push(...prefs.labelers.map((l) => l.did)); } else { dids.push(...prefs); } // fetch their definitions const labelers = await this.getLabelers({ dids, detailed: true, }); // assemble a map of labeler dids to the interpreted label value definitions const labelDefs = {}; if (labelers.data) { for (const labeler of labelers.data .views) { labelDefs[labeler.creator.did] = (0, moderation_1.interpretLabelValueDefinitions)(labeler); } } return labelDefs; } async post(record) { record.createdAt || (record.createdAt = new Date().toISOString()); return this.app.bsky.feed.post.create({ repo: this.accountDid }, record); } async deletePost(postUri) { this.assertAuthenticated(); const postUrip = new syntax_1.AtUri(postUri); return this.app.bsky.feed.post.delete({ repo: postUrip.hostname, rkey: postUrip.rkey, }); } async like(uri, cid, via) { return this.app.bsky.feed.like.create({ repo: this.accountDid }, { subject: { uri, cid }, createdAt: new Date().toISOString(), via, }); } async deleteLike(likeUri) { this.assertAuthenticated(); const likeUrip = new syntax_1.AtUri(likeUri); return this.app.bsky.feed.like.delete({ repo: likeUrip.hostname, rkey: likeUrip.rkey, }); } async repost(uri, cid, via) { return this.app.bsky.feed.repost.create({ repo: this.accountDid }, { subject: { uri, cid }, createdAt: new Date().toISOString(), via, }); } async deleteRepost(repostUri) { this.assertAuthenticated(); const repostUrip = new syntax_1.AtUri(repostUri); return this.app.bsky.feed.repost.delete({ repo: repostUrip.hostname, rkey: repostUrip.rkey, }); } async follow(subjectDid) { return this.app.bsky.graph.follow.create({ repo: this.accountDid }, { subject: subjectDid, createdAt: new Date().toISOString(), }); } async deleteFollow(followUri) { this.assertAuthenticated(); const followUrip = new syntax_1.AtUri(followUri); return this.app.bsky.graph.follow.delete({ repo: followUrip.hostname, rkey: followUrip.rkey, }); } /** * @note: Using this method will reset the whole profile record if it * previously contained invalid values (wrt to the profile lexicon). */ async upsertProfile(updateFn) { const upsert = async () => { const repo = this.assertDid; const collection = 'app.bsky.actor.profile'; const existing = await this.com.atproto.repo .getRecord({ repo, collection, rkey: 'self' }) .catch((_) => undefined); const existingRecord = existing && predicate.isValidProfile(existing.data.value) ? existing.data.value : undefined; // run the update const updated = await updateFn(existingRecord); // validate the value returned by the update function const validation = index_1.AppBskyActorProfile.validateRecord({ $type: collection, ...updated, }); if (!validation.success) { throw validation.error; } await this.com.atproto.repo.putRecord({ repo, collection, rkey: 'self', record: validation.value, swapRecord: existing?.data.cid || null, }); }; return (0, common_web_1.retry)(upsert, { maxRetries: 5, retryable: (e) => e instanceof index_1.ComAtprotoRepoPutRecord.InvalidSwapError, }); } async mute(actor) { return this.app.bsky.graph.muteActor({ actor }); } async unmute(actor) { return this.app.bsky.graph.unmuteActor({ actor }); } async muteModList(uri) { return this.app.bsky.graph.muteActorList({ list: uri }); } async unmuteModList(uri) { return this.app.bsky.graph.unmuteActorList({ list: uri }); } async blockModList(uri) { return this.app.bsky.graph.listblock.create({ repo: this.accountDid }, { subject: uri, createdAt: new Date().toISOString(), }); } async unblockModList(uri) { const repo = this.accountDid; const listInfo = await this.app.bsky.graph.getList({ list: uri, limit: 1, }); const blocked = listInfo.data.list.viewer?.blocked; if (blocked) { const { rkey } = new syntax_1.AtUri(blocked); return this.app.bsky.graph.listblock.delete({ repo, rkey, }); } } async updateSeenNotifications(seenAt = new Date().toISOString()) { return this.app.bsky.notification.updateSeen({ seenAt }); } async getPreferences() { const prefs = { feeds: { saved: undefined, pinned: undefined, }, // @ts-ignore populating below savedFeeds: undefined, feedViewPrefs: { home: { ...FEED_VIEW_PREF_DEFAULTS, }, }, threadViewPrefs: { ...THREAD_VIEW_PREF_DEFAULTS }, moderationPrefs: { adultContentEnabled: false, labels: { ...labels_1.DEFAULT_LABEL_SETTINGS }, labelers: this.appLabelers.map((did) => ({ did, labels: {}, })), mutedWords: [], hiddenPosts: [], }, birthDate: undefined, interests: { tags: [], }, bskyAppState: { queuedNudges: [], activeProgressGuide: undefined, nuxs: [], }, postInteractionSettings: { threadgateAllowRules: undefined, postgateEmbeddingRules: undefined, }, verificationPrefs: { hideBadges: false, }, }; const res = await this.app.bsky.actor.getPreferences({}); const labelPrefs = []; for (const pref of res.data.preferences) { if (predicate.isValidAdultContentPref(pref)) { // adult content preferences prefs.moderationPrefs.adultContentEnabled = pref.enabled; } else if (predicate.isValidContentLabelPref(pref)) { // content label preference const adjustedPref = adjustLegacyContentLabelPref(pref); labelPrefs.push(adjustedPref); } else if (predicate.isValidLabelersPref(pref)) { // labelers preferences prefs.moderationPrefs.labelers = this.appLabelers .map((did) => ({ did, labels: {} })) .concat(pref.labelers.map((labeler) => ({ ...labeler, labels: {}, }))); } else if (predicate.isValidSavedFeedsPrefV2(pref)) { prefs.savedFeeds = pref.items; } else if (predicate.isValidSavedFeedsPref(pref)) { // saved and pinned feeds prefs.feeds.saved = pref.saved; prefs.feeds.pinned = pref.pinned; } else if (predicate.isValidPersonalDetailsPref(pref)) { // birth date (irl) if (pref.birthDate) { prefs.birthDate = new Date(pref.birthDate); } } else if (predicate.isValidFeedViewPref(pref)) { // feed view preferences const { $type: _, feed, ...v } = pref; prefs.feedViewPrefs[feed] = { ...FEED_VIEW_PREF_DEFAULTS, ...v }; } else if (predicate.isValidThreadViewPref(pref)) { // thread view preferences const { $type: _, ...v } = pref; prefs.threadViewPrefs = { ...prefs.threadViewPrefs, ...v }; } else if (predicate.isValidInterestsPref(pref)) { const { $type: _, ...v } = pref; prefs.interests = { ...prefs.interests, ...v }; } else if (predicate.isValidMutedWordsPref(pref)) { prefs.moderationPrefs.mutedWords = pref.items; if (prefs.moderationPrefs.mutedWords.length) { prefs.moderationPrefs.mutedWords = prefs.moderationPrefs.mutedWords.map((word) => { word.actorTarget = word.actorTarget || 'all'; return word; }); } } else if (predicate.isValidHiddenPostsPref(pref)) { prefs.moderationPrefs.hiddenPosts = pref.items; } else if (predicate.isValidBskyAppStatePref(pref)) { prefs.bskyAppState.queuedNudges = pref.queuedNudges || []; prefs.bskyAppState.activeProgressGuide = pref.activeProgressGuide; prefs.bskyAppState.nuxs = pref.nuxs || []; } else if (predicate.isValidPostInteractionSettingsPref(pref)) { prefs.postInteractionSettings.threadgateAllowRules = pref.threadgateAllowRules; prefs.postInteractionSettings.postgateEmbeddingRules = pref.postgateEmbeddingRules; } else if (predicate.isValidVerificationPrefs(pref)) { prefs.verificationPrefs = { hideBadges: pref.hideBadges, }; } } /* * If `prefs.savedFeeds` is undefined, no `savedFeedsPrefV2` exists, which * means we want to try to migrate if needed. * * If v1 prefs exist, they will be migrated to v2. * * If no v1 prefs exist, the user is either new, or could be old and has * never edited their feeds. */ if (prefs.savedFeeds == null) { const { saved, pinned } = prefs.feeds; if (saved && pinned) { const uniqueMigratedSavedFeeds = new Map(); // insert Following feed first uniqueMigratedSavedFeeds.set('timeline', { id: common_web_1.TID.nextStr(), type: 'timeline', value: 'following', pinned: true, }); // use pinned as source of truth for feed order for (const uri of pinned) { const type = (0, util_1.getSavedFeedType)(uri); // only want supported types if (type === 'unknown') continue; uniqueMigratedSavedFeeds.set(uri, { id: common_web_1.TID.nextStr(), type, value: uri, pinned: true, }); } for (const uri of saved) { if (!uniqueMigratedSavedFeeds.has(uri)) { const type = (0, util_1.getSavedFeedType)(uri); // only want supported types if (type === 'unknown') continue; uniqueMigratedSavedFeeds.set(uri, { id: common_web_1.TID.nextStr(), type, value: uri, pinned: false, }); } } prefs.savedFeeds = Array.from(uniqueMigratedSavedFeeds.values()); } else { prefs.savedFeeds = [ { id: common_web_1.TID.nextStr(), type: 'timeline', value: 'following', pinned: true, }, ]; } // save to user preferences so this migration doesn't re-occur await this.overwriteSavedFeeds(prefs.savedFeeds); } // apply the label prefs for (const pref of labelPrefs) { if (pref.labelerDid) { const labeler = prefs.moderationPrefs.labelers.find((labeler) => labeler.did === pref.labelerDid); if (!labeler) continue; labeler.labels[pref.label] = pref.visibility; } else { prefs.moderationPrefs.labels[pref.label] = pref.visibility; } } prefs.moderationPrefs.labels = remapLegacyLabels(prefs.moderationPrefs.labels); // automatically configure the client this.configureLabelers(prefsArrayToLabelerDids(res.data.preferences)); return prefs; } async overwriteSavedFeeds(savedFeeds) { savedFeeds.forEach(util_1.validateSavedFeed); const uniqueSavedFeeds = new Map(); savedFeeds.forEach((feed) => { // remove and re-insert to preserve order if (uniqueSavedFeeds.has(feed.id)) { uniqueSavedFeeds.delete(feed.id); } uniqueSavedFeeds.set(feed.id, feed); }); return this.updateSavedFeedsV2Preferences(() => Array.from(uniqueSavedFeeds.values())); } async updateSavedFeeds(savedFeedsToUpdate) { savedFeedsToUpdate.map(util_1.validateSavedFeed); return this.updateSavedFeedsV2Preferences((savedFeeds) => { return savedFeeds.map((savedFeed) => { const updatedVersion = savedFeedsToUpdate.find((updated) => savedFeed.id === updated.id); if (updatedVersion) { return { ...savedFeed, // only update pinned pinned: updatedVersion.pinned, }; } return savedFeed; }); }); } async addSavedFeeds(savedFeeds) { const toSave = savedFeeds.map((f) => ({ ...f, id: common_web_1.TID.nextStr(), })); toSave.forEach(util_1.validateSavedFeed); return this.updateSavedFeedsV2Preferences((savedFeeds) => [ ...savedFeeds, ...toSave, ]); } async removeSavedFeeds(ids) { return this.updateSavedFeedsV2Preferences((savedFeeds) => [ ...savedFeeds.filter((feed) => !ids.find((id) => feed.id === id)), ]); } /** * @deprecated use `overwriteSavedFeeds` */ async setSavedFeeds(saved, pinned) { return this.updateFeedPreferences(() => ({ saved, pinned, })); } /** * @deprecated use `addSavedFeeds` */ async addSavedFeed(v) { return this.updateFeedPreferences((saved, pinned) => ({ saved: [...saved.filter((uri) => uri !== v), v], pinned, })); } /** * @deprecated use `removeSavedFeeds` */ async removeSavedFeed(v) { return this.updateFeedPreferences((saved, pinned) => ({ saved: saved.filter((uri) => uri !== v), pinned: pinned.filter((uri) => uri !== v), })); } /** * @deprecated use `addSavedFeeds` or `updateSavedFeeds` */ async addPinnedFeed(v) { return this.updateFeedPreferences((saved, pinned) => ({ saved: [...saved.filter((uri) => uri !== v), v], pinned: [...pinned.filter((uri) => uri !== v), v], })); } /** * @deprecated use `updateSavedFeeds` or `removeSavedFeeds` */ async removePinnedFeed(v) { return this.updateFeedPreferences((saved, pinned) => ({ saved, pinned: pinned.filter((uri) => uri !== v), })); } async setAdultContentEnabled(v) { await this.updatePreferences((prefs) => { const adultContentPref = prefs.findLast(predicate.isValidAdultContentPref) || { $type: 'app.bsky.actor.defs#adultContentPref', enabled: v, }; adultContentPref.enabled = v; return prefs .filter((pref) => !index_1.AppBskyActorDefs.isAdultContentPref(pref)) .concat(adultContentPref); }); } async setContentLabelPref(key, value, labelerDid) { if (labelerDid) { (0, syntax_1.ensureValidDid)(labelerDid); } await this.updatePreferences((prefs) => { const labelPref = prefs .filter(predicate.isValidContentLabelPref) .findLast((pref) => pref.label === key && pref.labelerDid === labelerDid) || { $type: 'app.bsky.actor.defs#contentLabelPref', label: key, labelerDid, visibility: value, }; labelPref.visibility = value; let legacyLabelPref; if (index_1.AppBskyActorDefs.isContentLabelPref(labelPref)) { // is global if (!labelPref.labelerDid) { const legacyLabelValue = { 'graphic-media': 'gore', porn: 'nsfw', sexual: 'suggestive', // Protect against using toString, hasOwnProperty, etc. as a label: __proto__: null, }[labelPref.label]; // if it's a legacy label, double-write the legacy label if (legacyLabelValue) { legacyLabelPref = prefs .filter(predicate.isValidContentLabelPref) .findLast((pref) => pref.label === legacyLabelValue && pref.labelerDid === undefined) || { $type: 'app.bsky.actor.defs#contentLabelPref', label: legacyLabelValue, labelerDid: undefined, visibility: value, }; legacyLabelPref.visibility = value; } } } return prefs .filter((pref) => !index_1.AppBskyActorDefs.isContentLabelPref(pref) || !(pref.label === key && pref.labelerDid === labelerDid)) .concat(labelPref) .filter((pref) => { if (!legacyLabelPref) return true; return (!index_1.AppBskyActorDefs.isContentLabelPref(pref) || !(pref.label === legacyLabelPref.label && pref.labelerDid === undefined)); }) .concat(legacyLabelPref ? [legacyLabelPref] : []); }); } async addLabeler(did) { const prefs = await this.updatePreferences((prefs) => { const labelersPref = prefs.findLast(predicate.isValidLabelersPref) || { $type: 'app.bsky.actor.defs#labelersPref', labelers: [], }; if (!labelersPref.labelers.some((labeler) => labeler.did === did)) { labelersPref.labelers.push({ did }); } return prefs .filter((pref) => !index_1.AppBskyActorDefs.isLabelersPref(pref)) .concat(labelersPref); }); // automatically configure the client this.configureLabelers(prefsArrayToLabelerDids(prefs)); } async removeLabeler(did) { const prefs = await this.updatePreferences((prefs) => { const labelersPref = prefs.findLast(predicate.isValidLabelersPref) || { $type: 'app.bsky.actor.defs#labelersPref', labelers: [], }; labelersPref.labelers = labelersPref.labelers.filter((l) => l.did !== did); return prefs .filter((pref) => !index_1.AppBskyActorDefs.isLabelersPref(pref)) .concat(labelersPref); }); // automatically configure the client this.configureLabelers(prefsArrayToLabelerDids(prefs)); } async setPersonalDetails({ birthDate, }) { await this.updatePreferences((prefs) => { const personalDetailsPref = prefs.findLast(predicate.isValidPersonalDetailsPref) || { $type: 'app.bsky.actor.defs#personalDetailsPref', }; personalDetailsPref.birthDate = birthDate instanceof Date ? birthDate.toISOString() : birthDate; return prefs .filter((pref) => !index_1.AppBskyActorDefs.isPersonalDetailsPref(pref)) .concat(personalDetailsPref); }); } async setFeedViewPrefs(feed, pref) { await this.updatePreferences((prefs) => { const existing = prefs .filter(predicate.isValidFeedViewPref) .findLast((pref) => pref.feed === feed); return prefs .filter((p) => !index_1.AppBskyActorDefs.isFeedViewPref(p) || p.feed !== feed) .concat({ ...existing, ...pref, $type: 'app.bsky.actor.defs#feedViewPref', feed, }); }); } async setThreadViewPrefs(pref) { await this.updatePreferences((prefs) => { const existing = prefs.findLast(predicate.isValidThreadViewPref); return prefs .filter((p) => !index_1.AppBskyActorDefs.isThreadViewPref(p)) .concat({ ...existing, ...pref, $type: 'app.bsky.actor.defs#threadViewPref', }); }); } async setInterestsPref(pref) { await this.updatePreferences((prefs) => { const existing = prefs.findLast(predicate.isValidInterestsPref); return prefs .filter((p) => !index_1.AppBskyActorDefs.isInterestsPref(p)) .concat({ ...existing, ...pref, $type: 'app.bsky.actor.defs#interestsPref', }); }); } /** * Add a muted word to user preferences. */ async addMutedWord(mutedWord) { const sanitizedValue = (0, util_1.sanitizeMutedWordValue)(mutedWord.value); if (!sanitizedValue) return; await this.updatePreferences((prefs) => { let mutedWordsPref = prefs.findLast(predicate.isValidMutedWordsPref); const newMutedWord = { id: common_web_1.TID.nextStr(), value: sanitizedValue, targets: mutedWord.targets || [], actorTarget: mutedWord.actorTarget || 'all', expiresAt: mutedWord.expiresAt || undefined, }; if (mutedWordsPref) { mutedWordsPref.items.push(newMutedWord); /** * Migrate any old muted words that don't have an id */ mutedWordsPref.items = migrateLegacyMutedWordsItems(mutedWordsPref.items); } else { // if the pref doesn't exist, create it mutedWordsPref = { $type: 'app.bsky.actor.defs#mutedWordsPref', items: [newMutedWord], }; } return prefs .filter((p) => !index_1.AppBskyActorDefs.isMutedWordsPref(p)) .concat(mutedWordsPref); }); } /** * Convenience method to add muted words to user preferences */ async addMutedWords(newMutedWords) { await Promise.all(newMutedWords.map((word) => this.addMutedWord(word))); } /** * @deprecated use `addMutedWords` or `addMutedWord` instead */ async upsertMutedWords(mutedWords) { await this.addMutedWords(mutedWords); } /** * Update a muted word in user preferences. */ async updateMutedWord(mutedWord) { await this.updatePreferences((prefs) => { const mutedWordsPref = prefs.findLast(predicate.isValidMutedWordsPref); if (mutedWordsPref) { mutedWordsPref.items = mutedWordsPref.items.map((existingItem) => { const match = matchMutedWord(existingItem, mutedWord); if (match) { const updated = { ...existingItem, ...mutedWord, }; return { id: existingItem.id || common_web_1.TID.nextStr(), value: (0, util_1.sanitizeMutedWordValue)(updated.value) || existingItem.value, targets: updated.targets || [], actorTarget: updated.actorTarget || 'all', expiresAt: updated.expiresAt || undefined, }; } else { return existingItem; } }); /** * Migrate any old muted words that don't have an id */ mutedWordsPref.items = migrateLegacyMutedWordsItems(mutedWordsPref.items); return prefs .filter((p) => !index_1.AppBskyActorDefs.isMutedWordsPref(p)) .concat(mutedWordsPref); } return prefs; }); } /** * Remove a muted word from user preferences. */ async removeMutedWord(mutedWord) { await this.updatePreferences((prefs) => { const mutedWordsPref = prefs.findLast(predicate.isValidMutedWordsPref); if (mutedWordsPref) { for (let i = 0; i < mutedWordsPref.items.length; i++) { const match = matchMutedWord(mutedWordsPref.items[i], mutedWord); if (match) { mutedWordsPref.items.splice(i, 1); break; } } /** * Migrate any old muted words that don't have an id */ mutedWordsPref.items = migrateLegacyMutedWordsItems(mutedWordsPref.items); return prefs .filter((p) => !index_1.AppBskyActorDefs.isMutedWordsPref(p)) .concat(mutedWordsPref); } return prefs; }); } /** * Convenience method to remove muted words from user preferences */ async removeMutedWords(mutedWords) { await Promise.all(mutedWords.map((word) => this.removeMutedWord(word))); } async hidePost(postUri) { await this.updateHiddenPost(postUri, 'hide'); } async unhidePost(postUri) { await this.updateHiddenPost(postUri, 'unhide'); } async bskyAppQueueNudges(nudges) { await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidBskyAppStatePref) || { $type: 'app.bsky.actor.defs#bskyAppStatePref', }; pref.queuedNudges = (pref.queuedNudges || []).concat(nudges); return prefs .filter((p) => !index_1.AppBskyActorDefs.isBskyAppStatePref(p)) .concat(pref); }); } async bskyAppDismissNudges(nudges) { await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidBskyAppStatePref) || { $type: 'app.bsky.actor.defs#bskyAppStatePref', }; nudges = Array.isArray(nudges) ? nudges : [nudges]; pref.queuedNudges = (pref.queuedNudges || []).filter((nudge) => !nudges.includes(nudge)); return prefs .filter((p) => !index_1.AppBskyActorDefs.isBskyAppStatePref(p)) .concat(pref); }); } async bskyAppSetActiveProgressGuide(guide) { if (guide) { const result = index_1.AppBskyActorDefs.validateBskyAppProgressGuide(guide); if (!result.success) throw result.error; } await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidBskyAppStatePref) || { $type: 'app.bsky.actor.defs#bskyAppStatePref', }; pref.activeProgressGuide = guide; return prefs .filter((p) => !index_1.AppBskyActorDefs.isBskyAppStatePref(p)) .concat(pref); }); } /** * Insert or update a NUX in user prefs */ async bskyAppUpsertNux(nux) { (0, util_1.validateNux)(nux); await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidBskyAppStatePref) || { $type: 'app.bsky.actor.defs#bskyAppStatePref', }; pref.nuxs = pref.nuxs || []; const existing = pref.nuxs?.find((n) => { return n.id === nux.id; }); let next; if (existing) { next = { id: existing.id, completed: nux.completed, data: nux.data, expiresAt: nux.expiresAt, }; } else { next = nux; } // remove duplicates and append pref.nuxs = pref.nuxs.filter((n) => n.id !== nux.id).concat(next); return prefs .filter((p) => !index_1.AppBskyActorDefs.isBskyAppStatePref(p)) .concat(pref); }); } /** * Removes NUXs from user preferences. */ async bskyAppRemoveNuxs(ids) { await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidBskyAppStatePref) || { $type: 'app.bsky.actor.defs#bskyAppStatePref', }; pref.nuxs = (pref.nuxs || []).filter((nux) => !ids.includes(nux.id)); return prefs .filter((p) => !index_1.AppBskyActorDefs.isBskyAppStatePref(p)) .concat(pref); }); } async setPostInteractionSettings(settings) { const result = index_1.AppBskyActorDefs.validatePostInteractionSettingsPref(settings); // Fool-proofing (should not be needed because of type safety) if (!result.success) throw result.error; await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidPostInteractionSettingsPref) || { $type: 'app.bsky.actor.defs#postInteractionSettingsPref', }; /** * Matches handling of `threadgate.allow` where `undefined` means "everyone" */ pref.threadgateAllowRules = settings.threadgateAllowRules; pref.postgateEmbeddingRules = settings.postgateEmbeddingRules; return prefs .filter((p) => !index_1.AppBskyActorDefs.isPostInteractionSettingsPref(p)) .concat(pref); }); } async setVerificationPrefs(settings) { const result = index_1.AppBskyActorDefs.validateVerificationPrefs(settings); // Fool-proofing (should not be needed because of type safety) if (!result.success) throw result.error; await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidVerificationPrefs) || { $type: 'app.bsky.actor.defs#verificationPrefs', hideBadges: false, }; pref.hideBadges = settings.hideBadges; return prefs .filter((p) => !index_1.AppBskyActorDefs.isVerificationPrefs(p)) .concat(pref); }); } /** * This function updates the preferences of a user and allows for a callback function to be executed * before the update. * @param cb - cb is a callback function that takes in a single parameter of type * AppBskyActorDefs.Preferences and returns either a boolean or void. This callback function is used to * update the preferences of the user. The function is called with the current preferences as an * argument and if the callback returns false, the preferences are not updated. */ async updatePreferences(cb) { try { await __classPrivateFieldGet(this, _Agent_prefsLock, "f").acquireAsync(); const res = await this.app.bsky.actor.getPreferences({}); const newPrefs = cb(res.data.preferences); if (newPrefs === false) { return res.data.preferences; } await this.app.bsky.actor.putPreferences({ preferences: newPrefs, }); return newPrefs; } finally { __classPrivateFieldGet(this, _Agent_prefsLock, "f").release(); } } async updateHiddenPost(postUri, action) { await this.updatePreferences((prefs) => { const pref = prefs.findLast(predicate.isValidHiddenPostsPref) || { $type: 'app.bsky.actor.defs#hiddenPostsPref', items: [], }; const hiddenItems = new Set(pref.items); if (action === 'hide') hiddenItems.add(postUri); else hiddenItems.delete(postUri); pref.items = [...hiddenItems]; return prefs .filter((p) => !index_1.AppBskyActorDefs.isHiddenPostsPref(p)) .concat(pref); }); } /** * A helper specifically for updating feed preferences */ async updateFeedPreferences(cb) { let res; await this.updatePreferences((prefs) => { const feedsPref = prefs.findLast(predicate.isValidSavedFeedsPref) || { $type: 'app.bsky.actor.defs#savedFeedsPref', saved: [], pinned: [], }; res = cb(feedsPref.s