A Raycast extension to search and manage Semble Cards and Collections
1import { BskyAgent } from "@atproto/api";
2import { getPreferenceValues } from "@raycast/api";
3import { SembleCard, SembleCollection, SembleCollectionLink, CardWithCollection } from "./types";
4
5interface Preferences {
6 identifier: string;
7 password: string;
8 pdsHost?: string;
9}
10
11export class SembleClient {
12 private agent: BskyAgent;
13 private preferences: Preferences;
14 private userDid: string | null = null;
15
16 constructor() {
17 this.preferences = getPreferenceValues<Preferences>();
18 const pdsHost = this.preferences.pdsHost || "bsky.social";
19 this.agent = new BskyAgent({
20 service: `https://${pdsHost}`,
21 });
22 }
23
24 async authenticate(): Promise<void> {
25 try {
26 const response = await this.agent.login({
27 identifier: this.preferences.identifier,
28 password: this.preferences.password,
29 });
30 this.userDid = response.data.did;
31 } catch (error) {
32 throw new Error(`Authentication failed: ${error instanceof Error ? error.message : "Unknown error"}`);
33 }
34 }
35
36 async getCards(): Promise<SembleCard[]> {
37 if (!this.userDid) {
38 await this.authenticate();
39 }
40
41 try {
42 const response = await this.agent.com.atproto.repo.listRecords({
43 repo: this.userDid!,
44 collection: "network.cosmik.card",
45 limit: 100,
46 });
47
48 return response.data.records as unknown as SembleCard[];
49 } catch (error) {
50 throw new Error(`Failed to fetch cards: ${error instanceof Error ? error.message : "Unknown error"}`);
51 }
52 }
53
54 async getCollections(): Promise<SembleCollection[]> {
55 if (!this.userDid) {
56 await this.authenticate();
57 }
58
59 try {
60 const response = await this.agent.com.atproto.repo.listRecords({
61 repo: this.userDid!,
62 collection: "network.cosmik.collection",
63 limit: 100,
64 });
65
66 return response.data.records as unknown as SembleCollection[];
67 } catch (error) {
68 throw new Error(`Failed to fetch collections: ${error instanceof Error ? error.message : "Unknown error"}`);
69 }
70 }
71
72 async getCollectionLinks(): Promise<SembleCollectionLink[]> {
73 if (!this.userDid) {
74 await this.authenticate();
75 }
76
77 try {
78 const response = await this.agent.com.atproto.repo.listRecords({
79 repo: this.userDid!,
80 collection: "network.cosmik.collectionLink",
81 limit: 100,
82 });
83
84 return response.data.records as unknown as SembleCollectionLink[];
85 } catch (error) {
86 throw new Error(
87 `Failed to fetch collection links: ${error instanceof Error ? error.message : "Unknown error"}`
88 );
89 }
90 }
91
92 async getCardsWithCollections(): Promise<CardWithCollection[]> {
93 const [cards, collections, collectionLinks] = await Promise.all([
94 this.getCards(),
95 this.getCollections(),
96 this.getCollectionLinks(),
97 ]);
98
99 // Create a map of collection URIs to collection objects
100 const collectionsMap = new Map<string, SembleCollection>();
101 collections.forEach((col) => {
102 collectionsMap.set(col.uri, col);
103 });
104
105 // Create a map of card URIs to their collections
106 const cardCollectionsMap = new Map<string, SembleCollection[]>();
107 collectionLinks.forEach((link) => {
108 const cardUri = link.value.card.uri;
109 const collectionUri = link.value.collection.uri;
110 const collection = collectionsMap.get(collectionUri);
111
112 if (collection) {
113 if (!cardCollectionsMap.has(cardUri)) {
114 cardCollectionsMap.set(cardUri, []);
115 }
116 cardCollectionsMap.get(cardUri)!.push(collection);
117 }
118 });
119
120 // Combine cards with their collections
121 return cards.map((card) => ({
122 card,
123 collections: cardCollectionsMap.get(card.uri) || [],
124 }));
125 }
126
127 getUserIdentifier(): string {
128 return this.preferences.identifier;
129 }
130
131 getUserDid(): string | null {
132 return this.userDid;
133 }
134}