diff --git a/src-tauri/src/commands/metadata.rs b/src-tauri/src/commands/metadata.rs index 1b1e33e4..bafcf24c 100644 --- a/src-tauri/src/commands/metadata.rs +++ b/src-tauri/src/commands/metadata.rs @@ -381,6 +381,35 @@ pub async fn get_all_local_interests( Ok(alt_events) } +#[tauri::command] +#[specta::specta] +pub async fn get_relay_list(id: String, state: State<'_, Nostr>) -> Result { + let client = &state.client; + let public_key = PublicKey::parse(&id).map_err(|e| e.to_string())?; + + let filter = Filter::new() + .author(public_key) + .kind(Kind::RelayList) + .limit(1); + + let mut events = Events::new(&[filter.clone()]); + + let mut rx = client + .stream_events(vec![filter], Some(Duration::from_secs(3))) + .await + .map_err(|e| e.to_string())?; + + while let Some(event) = rx.next().await { + events.insert(event); + } + + if let Some(event) = events.first() { + Ok(event.as_json()) + } else { + Err("Relay list not found".into()) + } +} + #[tauri::command] #[specta::specta] pub async fn get_all_profiles(state: State<'_, Nostr>) -> Result, String> { diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 9ee49265..dc69a429 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -67,11 +67,11 @@ impl Default for Settings { pub const DEFAULT_DIFFICULTY: u8 = 0; pub const FETCH_LIMIT: usize = 50; -pub const QUEUE_DELAY: u64 = 300; +pub const QUEUE_DELAY: u64 = 150; pub const NOTIFICATION_SUB_ID: &str = "lume_notification"; fn main() { - // tracing_subscriber::fmt::init(); + tracing_subscriber::fmt::init(); let builder = Builder::::new().commands(collect_commands![ get_relays, @@ -105,6 +105,7 @@ fn main() { get_interest, get_all_interests, get_all_local_interests, + get_relay_list, set_wallet, load_wallet, remove_wallet, diff --git a/src/commands.gen.ts b/src/commands.gen.ts index ad529397..401edaca 100644 --- a/src/commands.gen.ts +++ b/src/commands.gen.ts @@ -248,6 +248,14 @@ async getAllLocalInterests(until: string | null) : Promise> { + try { + return { status: "ok", data: await TAURI_INVOKE("get_relay_list", { id }) }; +} catch (e) { + if(e instanceof Error) throw e; + else return { status: "error", error: e as any }; +} +}, async setWallet(uri: string) : Promise> { try { return { status: "ok", data: await TAURI_INVOKE("set_wallet", { uri }) }; diff --git a/src/commons.ts b/src/commons.ts index 2cfcbca4..2962feeb 100644 --- a/src/commons.ts +++ b/src/commons.ts @@ -21,6 +21,15 @@ export function cn(...inputs: ClassValue[]) { return twMerge(clsx(inputs)); } +export function isValidRelayUrl(string: string) { + try { + const newUrl = new URL(string); + return newUrl.protocol === "ws:" || newUrl.protocol === "wss:"; + } catch (err) { + return false; + } +} + export const isImagePath = (path: string) => { const exts = ["jpg", "jpeg", "gif", "png", "webp", "avif", "tiff"]; diff --git a/src/components/note/buttons/quote.tsx b/src/components/note/buttons/quote.tsx index 5dd38051..141653bc 100644 --- a/src/components/note/buttons/quote.tsx +++ b/src/components/note/buttons/quote.tsx @@ -16,7 +16,7 @@ export function NoteQuote({ @@ -193,6 +195,179 @@ function Newsfeeds() { ); } +function Relayfeeds() { + const { id } = Route.useParams(); + const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({ + queryKey: ["relays", id], + queryFn: async () => { + const res = await commands.getRelayList(id); + + if (res.status === "ok") { + const event: NostrEvent = JSON.parse(res.data); + return event; + } else { + throw new Error(res.error); + } + }, + refetchOnWindowFocus: false, + }); + + return ( +
+
+

Relayfeeds

+
+ + +
+
+
+ {isLoading ? ( +
+ + Loading... +
+ ) : isError ? ( +
+

{error?.message ?? "Error"}

+
+ ) : !data ? ( +
+

You don't have any relay list yet.

+
+ ) : ( +
+
+ {data?.tags.map((tag) => + tag[1]?.startsWith("wss://") ? ( +
+
+ {tag[1]} +
+ +
+ ) : null, + )} +
+
+ + + + + + +
+
+ )} +
+ +
+ +
+
+ ); +} + +function RelayForm() { + const [url, setUrl] = useState(""); + const [isPending, startTransition] = useTransition(); + + const submit = () => { + startTransition(async () => { + if (!isValidRelayUrl(url)) { + await message("Relay URL is not valid", { kind: "error" }); + return; + } + + await LumeWindow.openColumn({ + name: url, + label: `relays_${url.replace(/[^\w\s]/gi, "")}`, + url: `/columns/relays/${encodeURIComponent(url)}`, + }); + + setUrl(""); + }); + }; + + return ( +
+ +
+ setUrl(e.currentTarget.value)} + onKeyDown={(event) => { + if (event.key === "Enter") submit(); + }} + value={url} + disabled={isPending} + placeholder="wss://..." + spellCheck={false} + className="flex-1 px-3 bg-neutral-100 border-transparent rounded-lg h-9 dark:bg-neutral-900 placeholder:text-neutral-600 focus:border-blue-500 focus:ring-0 dark:placeholder:text-neutral-400" + /> + +
+
+ ); +} + function Interests() { const { id } = Route.useParams(); const { isLoading, isError, error, data, refetch, isRefetching } = useQuery({ @@ -332,14 +507,14 @@ function Interests() { type="button" onClick={() => LumeWindow.openColumn({ - name: "Interests", + name: "Browse Interests", url: "/columns/discover-interests", label: "discover_interests", }) } className="h-9 w-full px-3 flex items-center justify-between bg-neutral-200/50 hover:bg-neutral-200 rounded-lg dark:bg-neutral-800/50 dark:hover:bg-neutral-800" > - Discover interests + Browse interests @@ -391,22 +566,6 @@ function Core() { Add -
-
Relays
- -
{data?.map((column) => (