feat: add semisol.dev recommends api

This commit is contained in:
Kieran 2023-05-18 11:42:30 +01:00
parent c52eb38833
commit c82a0faf28
Signed by: Kieran
GPG Key ID: DE71CEB3925BE941
6 changed files with 96 additions and 8 deletions

View File

@ -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<HexKey[]>();
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 <PageSpinner />;
}, [login, provider]);
return (
<>
<h3>
<FormattedMessage defaultMessage="Suggested Follows" />
</h3>
<FollowListBase pubkeys={userList} showAbout={true} />
<div className="card flex f-space">
<FormattedMessage defaultMessage="Provider" />
<select onChange={e => setProvider(Number(e.target.value))}>
<option value={Provider.NostrBand}>nostr.band</option>
<option value={Provider.SemisolDev}>semisol.dev</option>
</select>
</div>
{error && <b className="error">{error}</b>}
{userList ? <FollowListBase pubkeys={userList} showAbout={true} /> : <PageSpinner />}
</>
);
}

43
packages/app/src/External/SemisolDev.ts vendored Normal file
View File

@ -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<string>) {
return await this.#json<RecommendedProfilesResponse>("POST", "/nosgraph/v1/recommend", {
pubkey,
exclude: [],
following: follows,
});
}
async #json<T>(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);
}
}
}

View File

@ -1 +1,2 @@
export * from "./NostrBand";
export * from "./SemisolDev";

View File

@ -19,6 +19,10 @@ const DataProviders = [
name: "nostr.band",
owner: bech32ToHex("npub1sx9rnd03vs34lp39fvfv5krwlnxpl90f3dzuk8y3cuwutk2gdhdqjz6g8m"),
},
{
name: "semisol.dev",
owner: bech32ToHex("npub12262qa4uhw7u8gdwlgmntqtv7aye8vdcmvszkqwgs0zchel6mz7s6cgrkj"),
},
{
name: "nostr.watch",
owner: bech32ToHex("npub1uac67zc9er54ln0kl6e4qp2y6ta3enfcg7ywnayshvlw9r5w6ehsqq99rx"),

View File

@ -382,6 +382,10 @@ input:disabled {
justify-content: flex-end;
}
.f-space {
justify-content: space-between;
}
.w-max {
width: 100%;
width: stretch;