diff --git a/packages/app/src/Element/SuggestedProfiles.tsx b/packages/app/src/Element/SuggestedProfiles.tsx index 7ba67e2e..319befe7 100644 --- a/packages/app/src/Element/SuggestedProfiles.tsx +++ b/packages/app/src/Element/SuggestedProfiles.tsx @@ -5,32 +5,68 @@ import { FormattedMessage } from "react-intl"; import FollowListBase from "Element/FollowListBase"; import PageSpinner from "Element/PageSpinner"; import NostrBandApi from "External/NostrBand"; +import SemisolDevApi from "External/SemisolDev"; import useLogin from "Hooks/useLogin"; import { hexToBech32 } from "Util"; +enum Provider { + NostrBand = 1, + SemisolDev = 2, +} + export default function SuggestedProfiles() { const login = useLogin(); const [userList, setUserList] = useState(); + const [provider, setProvider] = useState(Provider.NostrBand); + const [error, setError] = useState(""); async function loadSuggestedProfiles() { - const api = new NostrBandApi(); - const users = await api.sugguestedFollows(hexToBech32(NostrPrefix.PublicKey, login.publicKey)); - const keys = users.profiles.map(a => a.pubkey); - setUserList(keys); + if (!login.publicKey) return; + setUserList(undefined); + setError(""); + + try { + switch (provider) { + case Provider.NostrBand: { + const api = new NostrBandApi(); + const users = await api.sugguestedFollows(hexToBech32(NostrPrefix.PublicKey, login.publicKey)); + const keys = users.profiles.map(a => a.pubkey); + setUserList(keys); + break; + } + case Provider.SemisolDev: { + const api = new SemisolDevApi(); + const users = await api.sugguestedFollows(login.publicKey, login.follows.item); + const keys = users.recommendations.sort(a => a[1]).map(a => a[0]); + setUserList(keys); + break; + } + } + } catch (e) { + if (e instanceof Error) { + setError(e.message); + } + } } useEffect(() => { loadSuggestedProfiles().catch(console.error); - }, []); - - if (!userList) return ; + }, [login, provider]); return ( <>

- +
+ + +
+ {error && {error}} + {userList ? : } ); } diff --git a/packages/app/src/External/NostrBand.tsx b/packages/app/src/External/NostrBand.ts similarity index 100% rename from packages/app/src/External/NostrBand.tsx rename to packages/app/src/External/NostrBand.ts diff --git a/packages/app/src/External/SemisolDev.ts b/packages/app/src/External/SemisolDev.ts new file mode 100644 index 00000000..8e133d08 --- /dev/null +++ b/packages/app/src/External/SemisolDev.ts @@ -0,0 +1,43 @@ +export interface RecommendedProfilesResponse { + quality: number; + recommendations: Array<[pubkey: string, score: number]>; +} + +export class SemisolDevApiError extends Error { + body: string; + statusCode: number; + + constructor(message: string, body: string, status: number) { + super(message); + this.body = body; + this.statusCode = status; + } +} + +export default class SemisolDevApi { + readonly #url = "https://api.semisol.dev"; + + async sugguestedFollows(pubkey: string, follows: Array) { + return await this.#json("POST", "/nosgraph/v1/recommend", { + pubkey, + exclude: [], + following: follows, + }); + } + + async #json(method: string, path: string, body?: unknown) { + const url = `${this.#url}${path}`; + const res = await fetch(url, { + method: method ?? "GET", + body: body ? JSON.stringify(body) : undefined, + headers: { + ...(body ? { "content-type": "application/json" } : {}), + }, + }); + if (res.ok) { + return (await res.json()) as T; + } else { + throw new SemisolDevApiError(`Failed to load content from ${url}`, await res.text(), res.status); + } + } +} diff --git a/packages/app/src/External/index.ts b/packages/app/src/External/index.ts index 1ab06e0e..011459ad 100644 --- a/packages/app/src/External/index.ts +++ b/packages/app/src/External/index.ts @@ -1 +1,2 @@ export * from "./NostrBand"; +export * from "./SemisolDev"; diff --git a/packages/app/src/Pages/ZapPool.tsx b/packages/app/src/Pages/ZapPool.tsx index fd45f12a..95a889d1 100644 --- a/packages/app/src/Pages/ZapPool.tsx +++ b/packages/app/src/Pages/ZapPool.tsx @@ -19,6 +19,10 @@ const DataProviders = [ name: "nostr.band", owner: bech32ToHex("npub1sx9rnd03vs34lp39fvfv5krwlnxpl90f3dzuk8y3cuwutk2gdhdqjz6g8m"), }, + { + name: "semisol.dev", + owner: bech32ToHex("npub12262qa4uhw7u8gdwlgmntqtv7aye8vdcmvszkqwgs0zchel6mz7s6cgrkj"), + }, { name: "nostr.watch", owner: bech32ToHex("npub1uac67zc9er54ln0kl6e4qp2y6ta3enfcg7ywnayshvlw9r5w6ehsqq99rx"), diff --git a/packages/app/src/index.css b/packages/app/src/index.css index e3a44044..6d6c7fb9 100644 --- a/packages/app/src/index.css +++ b/packages/app/src/index.css @@ -382,6 +382,10 @@ input:disabled { justify-content: flex-end; } +.f-space { + justify-content: space-between; +} + .w-max { width: 100%; width: stretch;