forked from Kieran/snort
feat: add semisol.dev recommends api
This commit is contained in:
parent
c52eb38833
commit
c82a0faf28
@ -5,32 +5,68 @@ import { FormattedMessage } from "react-intl";
|
|||||||
import FollowListBase from "Element/FollowListBase";
|
import FollowListBase from "Element/FollowListBase";
|
||||||
import PageSpinner from "Element/PageSpinner";
|
import PageSpinner from "Element/PageSpinner";
|
||||||
import NostrBandApi from "External/NostrBand";
|
import NostrBandApi from "External/NostrBand";
|
||||||
|
import SemisolDevApi from "External/SemisolDev";
|
||||||
import useLogin from "Hooks/useLogin";
|
import useLogin from "Hooks/useLogin";
|
||||||
import { hexToBech32 } from "Util";
|
import { hexToBech32 } from "Util";
|
||||||
|
|
||||||
|
enum Provider {
|
||||||
|
NostrBand = 1,
|
||||||
|
SemisolDev = 2,
|
||||||
|
}
|
||||||
|
|
||||||
export default function SuggestedProfiles() {
|
export default function SuggestedProfiles() {
|
||||||
const login = useLogin();
|
const login = useLogin();
|
||||||
const [userList, setUserList] = useState<HexKey[]>();
|
const [userList, setUserList] = useState<HexKey[]>();
|
||||||
|
const [provider, setProvider] = useState(Provider.NostrBand);
|
||||||
|
const [error, setError] = useState("");
|
||||||
|
|
||||||
async function loadSuggestedProfiles() {
|
async function loadSuggestedProfiles() {
|
||||||
const api = new NostrBandApi();
|
if (!login.publicKey) return;
|
||||||
const users = await api.sugguestedFollows(hexToBech32(NostrPrefix.PublicKey, login.publicKey));
|
setUserList(undefined);
|
||||||
const keys = users.profiles.map(a => a.pubkey);
|
setError("");
|
||||||
setUserList(keys);
|
|
||||||
|
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(() => {
|
useEffect(() => {
|
||||||
loadSuggestedProfiles().catch(console.error);
|
loadSuggestedProfiles().catch(console.error);
|
||||||
}, []);
|
}, [login, provider]);
|
||||||
|
|
||||||
if (!userList) return <PageSpinner />;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h3>
|
<h3>
|
||||||
<FormattedMessage defaultMessage="Suggested Follows" />
|
<FormattedMessage defaultMessage="Suggested Follows" />
|
||||||
</h3>
|
</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
43
packages/app/src/External/SemisolDev.ts
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
1
packages/app/src/External/index.ts
vendored
1
packages/app/src/External/index.ts
vendored
@ -1 +1,2 @@
|
|||||||
export * from "./NostrBand";
|
export * from "./NostrBand";
|
||||||
|
export * from "./SemisolDev";
|
||||||
|
@ -19,6 +19,10 @@ const DataProviders = [
|
|||||||
name: "nostr.band",
|
name: "nostr.band",
|
||||||
owner: bech32ToHex("npub1sx9rnd03vs34lp39fvfv5krwlnxpl90f3dzuk8y3cuwutk2gdhdqjz6g8m"),
|
owner: bech32ToHex("npub1sx9rnd03vs34lp39fvfv5krwlnxpl90f3dzuk8y3cuwutk2gdhdqjz6g8m"),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "semisol.dev",
|
||||||
|
owner: bech32ToHex("npub12262qa4uhw7u8gdwlgmntqtv7aye8vdcmvszkqwgs0zchel6mz7s6cgrkj"),
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "nostr.watch",
|
name: "nostr.watch",
|
||||||
owner: bech32ToHex("npub1uac67zc9er54ln0kl6e4qp2y6ta3enfcg7ywnayshvlw9r5w6ehsqq99rx"),
|
owner: bech32ToHex("npub1uac67zc9er54ln0kl6e4qp2y6ta3enfcg7ywnayshvlw9r5w6ehsqq99rx"),
|
||||||
|
@ -382,6 +382,10 @@ input:disabled {
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.f-space {
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
.w-max {
|
.w-max {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
width: stretch;
|
width: stretch;
|
||||||
|
Loading…
Reference in New Issue
Block a user