From 2cf77a1bb09b4ea7cc1e7bb0ec77e2b7488c7246 Mon Sep 17 00:00:00 2001 From: mmattbtw Date: Sun, 14 Jun 2026 20:04:37 -0500 Subject: [PATCH] feat(amethyst): add opt-in popfeed sync Add a persisted settings toggle and sync manual scrobble submissions into Popfeed list/listItem records using the published social.popfeed lexicons. Skip duplicate track/album identifiers and cover the mapping/write behavior with focused tests. --- .../app/(tabs)/(stamp)/stamp/submit.tsx | 16 + apps/amethyst/app/(tabs)/settings/index.tsx | 20 +- apps/amethyst/lib/__tests__/popfeed.test.ts | 156 +++++++++ apps/amethyst/lib/popfeed.ts | 300 ++++++++++++++++++ apps/amethyst/stores/preferenceSlice.tsx | 4 + todo.md | 2 +- 6 files changed, 492 insertions(+), 6 deletions(-) create mode 100644 apps/amethyst/lib/__tests__/popfeed.test.ts create mode 100644 apps/amethyst/lib/popfeed.ts diff --git a/apps/amethyst/app/(tabs)/(stamp)/stamp/submit.tsx b/apps/amethyst/app/(tabs)/(stamp)/stamp/submit.tsx index 948cdd1..8d59b1c 100644 --- a/apps/amethyst/app/(tabs)/(stamp)/stamp/submit.tsx +++ b/apps/amethyst/app/(tabs)/(stamp)/stamp/submit.tsx @@ -7,6 +7,7 @@ import { Button } from "@/components/ui/button"; import { Text } from "@/components/ui/text"; import { Textarea } from "@/components/ui/textarea"; import { MusicBrainzRecording, PlaySubmittedData } from "@/lib/oldStamp"; +import { syncPlayToPopfeed } from "@/lib/popfeed"; import { cn } from "@/lib/utils"; import { useStore } from "@/stores/mainStore"; import { @@ -160,6 +161,7 @@ const createPlayRecord = (result: MusicBrainzRecording): PlayRecord => { export default function Submit() { const router = useRouter(); const agent = useStore((state) => state.pdsAgent); + const popfeedSyncEnabled = useStore((state) => state.popfeedSyncEnabled); const ctx = useContext(StampContext); const { state, setState } = ctx as StampContextValue; @@ -261,6 +263,20 @@ powered by @teal.fm`; playRecord: record, blueskyPostUrl: null, }; + if (popfeedSyncEnabled && agent) { + try { + await syncPlayToPopfeed(agent, { + play: record, + releaseDate: selectedTrack.selectedRelease?.date, + releaseGroupType: + selectedTrack.selectedRelease?.["release-group"]?.[ + "primary-type" + ], + }); + } catch (error) { + console.error("Failed to sync play to Popfeed:", error); + } + } if (shareWithBluesky && agent) { // lol this type const rt = new RichText({ diff --git a/apps/amethyst/app/(tabs)/settings/index.tsx b/apps/amethyst/app/(tabs)/settings/index.tsx index 25e645e..0eeace1 100644 --- a/apps/amethyst/app/(tabs)/settings/index.tsx +++ b/apps/amethyst/app/(tabs)/settings/index.tsx @@ -17,6 +17,10 @@ export default function Settings() { const { colorScheme, setColorScheme } = useColorScheme(); const appviewDid = useStore((state) => state.tealDid); const setAppviewDid = useStore((state) => state.setTealDid); + const popfeedSyncEnabled = useStore((state) => state.popfeedSyncEnabled); + const setPopfeedSyncEnabled = useStore( + (state) => state.setPopfeedSyncEnabled, + ); const colorSchemeOptions = [ { label: "Light", value: "light" }, @@ -47,6 +51,11 @@ export default function Settings() { onSubmit={(e) => setAppviewDid(e)} placeholder="Enter your Appview DID (e.g., did:web:...)" /> +