useCachedFetch hook for trending api calls
This commit is contained in:
103
packages/app/src/External/NostrBand.ts
vendored
103
packages/app/src/External/NostrBand.ts
vendored
@ -1,105 +1,20 @@
|
||||
import { throwIfOffline } from "@snort/shared";
|
||||
import { NostrEvent } from "@snort/system";
|
||||
|
||||
export interface TrendingUser {
|
||||
pubkey: string;
|
||||
}
|
||||
|
||||
export interface TrendingUserResponse {
|
||||
profiles: Array<TrendingUser>;
|
||||
}
|
||||
|
||||
export interface TrendingNote {
|
||||
event: NostrEvent;
|
||||
author: NostrEvent; // kind0 event
|
||||
}
|
||||
|
||||
export interface TrendingNoteResponse {
|
||||
notes: Array<TrendingNote>;
|
||||
}
|
||||
|
||||
export interface TrendingHashtagsResponse {
|
||||
hashtags: Array<{
|
||||
hashtag: string;
|
||||
posts: number;
|
||||
}>;
|
||||
}
|
||||
|
||||
export interface SuggestedFollow {
|
||||
pubkey: string;
|
||||
}
|
||||
|
||||
export interface SuggestedFollowsResponse {
|
||||
profiles: Array<SuggestedFollow>;
|
||||
}
|
||||
|
||||
export class NostrBandError extends Error {
|
||||
body: string;
|
||||
statusCode: number;
|
||||
|
||||
constructor(message: string, body: string, status: number) {
|
||||
super(message);
|
||||
this.body = body;
|
||||
this.statusCode = status;
|
||||
}
|
||||
}
|
||||
|
||||
export default class NostrBandApi {
|
||||
readonly #url = "https://api.nostr.band";
|
||||
readonly #supportedLangs = ["en", "de", "ja", "zh", "th", "pt", "es", "fr"];
|
||||
async trendingProfiles() {
|
||||
return await this.#json<TrendingUserResponse>("GET", "/v0/trending/profiles");
|
||||
|
||||
trendingProfilesUrl() {
|
||||
return `${this.#url}/v0/trending/profiles`;
|
||||
}
|
||||
|
||||
async trendingNotes(lang?: string) {
|
||||
if (lang && this.#supportedLangs.includes(lang)) {
|
||||
return await this.#json<TrendingNoteResponse>("GET", `/v0/trending/notes?lang=${lang}`);
|
||||
}
|
||||
return await this.#json<TrendingNoteResponse>("GET", "/v0/trending/notes");
|
||||
trendingNotesUrl(lang?: string) {
|
||||
return `${this.#url}/v0/trending/notes${lang && this.#supportedLangs.includes(lang) ? `?lang=${lang}` : ""}`;
|
||||
}
|
||||
|
||||
async sugguestedFollows(pubkey: string) {
|
||||
return await this.#json<SuggestedFollowsResponse>("GET", `/v0/suggested/profiles/${pubkey}`);
|
||||
suggestedFollowsUrl(pubkey: string) {
|
||||
return `${this.#url}/v0/suggested/profiles/${pubkey}`;
|
||||
}
|
||||
|
||||
async trendingHashtags(lang?: string) {
|
||||
if (lang && this.#supportedLangs.includes(lang)) {
|
||||
return await this.#json<TrendingHashtagsResponse>("GET", `/v0/trending/hashtags?lang=${lang}`);
|
||||
}
|
||||
return await this.#json<TrendingHashtagsResponse>("GET", "/v0/trending/hashtags");
|
||||
}
|
||||
|
||||
async #json<T>(method: string, path: string) {
|
||||
throwIfOffline();
|
||||
|
||||
const storageKey = `nostr-band-${path}`;
|
||||
const cachedData = localStorage.getItem(storageKey);
|
||||
if (cachedData) {
|
||||
const parsedData = JSON.parse(cachedData);
|
||||
const { timestamp, data } = parsedData;
|
||||
|
||||
const ageInMinutes = (new Date().getTime() - timestamp) / 1000 / 60;
|
||||
if (ageInMinutes < 15) {
|
||||
return data as T;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch(`${this.#url}${path}`, { method: method ?? "GET" });
|
||||
if (res.ok) {
|
||||
const data = (await res.json()) as T;
|
||||
// Cache the new data with a timestamp
|
||||
localStorage.setItem(storageKey, JSON.stringify({ data, timestamp: new Date().getTime() }));
|
||||
return data;
|
||||
} else {
|
||||
throw new NostrBandError("Failed to load content from nostr.band", await res.text(), res.status);
|
||||
}
|
||||
} catch (error) {
|
||||
if (cachedData) {
|
||||
return JSON.parse(cachedData).data as T;
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
trendingHashtagsUrl(lang?: string) {
|
||||
return `${this.#url}/v0/trending/hashtags${lang && this.#supportedLangs.includes(lang) ? `?lang=${lang}` : ""}`;
|
||||
}
|
||||
}
|
||||
|
44
packages/app/src/External/SemisolDev.ts
vendored
44
packages/app/src/External/SemisolDev.ts
vendored
@ -1,46 +1,8 @@
|
||||
import { throwIfOffline } from "@snort/shared";
|
||||
|
||||
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) {
|
||||
throwIfOffline();
|
||||
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);
|
||||
}
|
||||
suggestedFollowsUrl(pubkey: string, follows: Array<string>) {
|
||||
const query = new URLSearchParams({ pubkey, follows: JSON.stringify(follows) });
|
||||
return `${this.#url}/nosgraph/v1/recommend?${query.toString()}`;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user