···147147 getProfile(did) {
148148 return hostCall("getProfile", { did });
149149 }
150150+ // Like getProfile, but includes viewer relationship details not present
151151+ // on the basic profile view: viewer.following, viewer.followedBy, and
152152+ // viewer.knownFollowers (a summary of mutual followers).
153153+ getDetailedProfile(did) {
154154+ return hostCall("getDetailedProfile", { did });
155155+ }
156156+ // The full known-followers list for did (the summary on
157157+ // getDetailedProfile's viewer.knownFollowers is capped to a handful).
158158+ getKnownFollowers(did) {
159159+ return hostCall("getKnownFollowers", { did });
160160+ }
150161 getRecord(repo, collection, rkey) {
151162 return hostCall("getRecord", { repo, collection, rkey });
152163 }
···163174164175 refreshFeedFilters(feedURI = null) {
165176 return hostCall("refreshFeedFilters", feedURI);
177177+ }
178178+179179+ // Actions on behalf of the signed-in user. Each method requires the
180180+ // corresponding scope ("mute", "block", "feedFeedback") to be declared in
181181+ // the plugin manifest's `permissions.actions` array, which the user must
182182+ // grant at install time.
183183+ muteActor(did) {
184184+ return hostCall("muteActor", { did, mute: true });
185185+ }
186186+ unmuteActor(did) {
187187+ return hostCall("muteActor", { did, mute: false });
188188+ }
189189+ blockActor(did) {
190190+ return hostCall("blockActor", { did, block: true });
191191+ }
192192+ unblockActor(did) {
193193+ return hostCall("blockActor", { did, block: false });
194194+ }
195195+ // Acts like the user clicking "Show less like this": sends the requestLess
196196+ // feedback signal to the feed that served the post and collapses the post
197197+ // behind a feedback message in feeds.
198198+ showLessLikeThis(postUri, feedUri) {
199199+ return hostCall("showLessLikeThis", { postUri, feedUri });
200200+ }
201201+ showMoreLikeThis(postUri, feedUri) {
202202+ return hostCall("showMoreLikeThis", { postUri, feedUri });
166203 }
167204}
168205
···3838- Add custom feed filters
3939- Transform rich text in posts
4040- Make whitelisted network requests (requires permissions)
4141+- Read appview data with the current user as the viewer (profiles, posts, etc.)
4242+- Mute, block, or send feed feedback ("show more/less like this") on the user's behalf (requires permissions)
41434244### Plugins CANNOT:
4345
···632632 }
633633634634 async sendInteractions(interactions, feedProxyUrl) {
635635+ // Interactions are only useful to the feed generator that served the
636636+ // posts, so callers must route to one.
637637+ if (!feedProxyUrl) {
638638+ throw new Error("sendInteractions requires a feedProxyUrl");
639639+ }
635640 await this.request(`app.bsky.feed.sendInteractions`, {
636641 method: "POST",
637642 body: { interactions },
+11
src/js/dataHelpers.js
···5151 return item.type === "timeline" ? FOLLOWING_FEED_URI : item.data.uri;
5252}
53535454+// The atproto-proxy value that routes app.bsky.feed.sendInteractions (and
5555+// post-seen tracking) to a specific feed generator's own service, so
5656+// algorithmic feeds actually receive the signal instead of it silently
5757+// going nowhere useful.
5858+export function getFeedGeneratorProxyUrl(feedGenerator) {
5959+ if (!feedGenerator?.did) {
6060+ return null;
6161+ }
6262+ return `${feedGenerator.did}#bsky_fg`;
6363+}
6464+5465export function getRKey(record) {
5566 return record.uri.split("/").pop();
5667}
+2-2
src/js/dataLayer/dataStore.js
···1111 this.$chatRecipientSearchResults = new Signal.State(null);
1212 this.$searchTypeaheadResults = new Signal.State(null);
1313 this.$feedSearchResults = new Signal.State(null);
1414- this.$showLessInteractions = new Signal.State([]);
1515- this.$showMoreInteractions = new Signal.State([]);
1614 this.$notifications = new Signal.State(null);
1715 this.$mentionNotifications = new Signal.State(null);
1816 this.$pinnedItems = new Signal.State(null);
···3129 this.$latestPostSearchRequestTimeTop = new Signal.State(null);
3230 this.$latestPostSearchRequestTimeLatest = new Signal.State(null);
3331 // Keyed signals
3232+ this.$showLessInteractions = new SignalMap();
3333+ this.$showMoreInteractions = new SignalMap();
3434 this.$feeds = new SignalMap();
3535 this.$posts = new SignalMap();
3636 this.$embeddedPosts = new SignalMap();
+12
src/js/dataLayer/declarative.js
···2828 return profile;
2929 }
30303131+ async ensureKnownFollowers(profileDid) {
3232+ let knownFollowers = this.derived.$knownFollowers.get(profileDid);
3333+ if (!knownFollowers) {
3434+ await this.requests.loadKnownFollowers(profileDid);
3535+ knownFollowers = this.derived.$knownFollowers.get(profileDid);
3636+ }
3737+ if (!knownFollowers) {
3838+ throw new Error("Known followers not found");
3939+ }
4040+ return knownFollowers;
4141+ }
4242+3143 async ensureProfileFollows(profileDid) {
3244 let profileFollows = this.derived.$profileFollows.get(profileDid);
3345 if (!profileFollows) {