diff --git a/README.md b/README.md index a616015f..1481a772 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ Snort supports the following NIP's: - [x] NIP-65: Relay List Metadata - [x] NIP-75: Zap Goals - [x] NIP-78: App specific data -- [ ] NIP-89: App handlers +- [x] NIP-89: App handlers - [x] NIP-94: File Metadata - [x] NIP-96: HTTP File Storage Integration (Draft) - [x] NIP-98: HTTP Auth diff --git a/packages/app/src/Components/Event/Note/Note.tsx b/packages/app/src/Components/Event/Note/Note.tsx index 62d17c4e..c9969ed1 100644 --- a/packages/app/src/Components/Event/Note/Note.tsx +++ b/packages/app/src/Components/Event/Note/Note.tsx @@ -24,6 +24,7 @@ import { NoteProps } from "../EventComponent"; import HiddenNote from "../HiddenNote"; import Poll from "../Poll"; import NoteFooter from "./NoteFooter/NoteFooter"; +import NoteAppHandler from "./NoteAppHandler"; const defaultOptions = { showHeader: true, @@ -169,22 +170,9 @@ function Reaction({ ev }: { ev: TaggedNostrEvent }) { } function handleNonTextNote(ev: TaggedNostrEvent) { - const alt = findTag(ev, "alt"); - if (alt) { - return ( -
- -
- ); - } else if (ev.kind === EventKind.Reaction) { + if (ev.kind === EventKind.Reaction) { return ; } else { - return ( -
- }> -
{JSON.stringify(ev, undefined, "  ")}
-
-
- ); + return ; } } diff --git a/packages/app/src/Components/Event/Note/NoteAppHandler.tsx b/packages/app/src/Components/Event/Note/NoteAppHandler.tsx new file mode 100644 index 00000000..74258d9b --- /dev/null +++ b/packages/app/src/Components/Event/Note/NoteAppHandler.tsx @@ -0,0 +1,44 @@ +import { mapEventToProfile, NostrLink, TaggedNostrEvent } from "@snort/system"; +import { FormattedMessage } from "react-intl"; + +import Icon from "@/Components/Icons/Icon"; +import Avatar from "@/Components/User/Avatar"; +import DisplayName from "@/Components/User/DisplayName"; +import useAppHandler from "@/Hooks/useAppHandler"; + +export default function NoteAppHandler({ ev }: { ev: TaggedNostrEvent }) { + const handlers = useAppHandler(ev.kind); + const link = NostrLink.fromEvent(ev); + + const profiles = handlers.apps + .map(a => ({ profile: mapEventToProfile(a), event: a })) + .filter(a => a.profile) + .slice(0, 5); + + return ( +
+ + + + {profiles.map(a => ( +
+
+ +
+ +
+
+ { + const webHandler = a.event.tags.find(a => a[0] === "web" && a[2] === "nevent")?.[1]; + if (webHandler) { + window.open(webHandler.replace("", link.encode()), "_blank"); + } + }} + /> +
+ ))} +
+ ); +} diff --git a/packages/app/src/Components/User/DisplayName.tsx b/packages/app/src/Components/User/DisplayName.tsx index 96c2fbfa..d1647f72 100644 --- a/packages/app/src/Components/User/DisplayName.tsx +++ b/packages/app/src/Components/User/DisplayName.tsx @@ -12,9 +12,9 @@ interface DisplayNameProps { user?: UserMetadata | undefined; } -const DisplayName = ({ pubkey }: DisplayNameProps) => { - const profile = useUserProfile(pubkey); - const [name, isPlaceHolder] = useMemo(() => getDisplayNameOrPlaceHolder(profile, pubkey), [profile, pubkey]); +const DisplayName = ({ pubkey, user }: DisplayNameProps) => { + const profile = useUserProfile(user ? undefined : pubkey); + const [name, isPlaceHolder] = useMemo(() => getDisplayNameOrPlaceHolder(profile ?? user, pubkey), [profile, pubkey]); return {name}; }; diff --git a/packages/app/src/Hooks/useAppHandler.ts b/packages/app/src/Hooks/useAppHandler.ts new file mode 100644 index 00000000..3dbb0d20 --- /dev/null +++ b/packages/app/src/Hooks/useAppHandler.ts @@ -0,0 +1,31 @@ +import { EventKind, NostrLink, RequestBuilder } from "@snort/system"; +import { useRequestBuilder } from "@snort/system-react"; + +import useFollowsControls from "./useFollowControls"; + +export default function useAppHandler(kind: EventKind) { + const { followList } = useFollowsControls(); + + const sub = new RequestBuilder(`app-handler:${kind}`); + sub + .withFilter() + .kinds([31990 as EventKind]) + .tag("k", [kind.toString()]); + + const dataApps = useRequestBuilder(sub); + + const reccomendsSub = new RequestBuilder(`app-handler:${kind}:recommends`); + if (dataApps.length > 0 && followList.length > 0) { + reccomendsSub + .withFilter() + .kinds([31989 as EventKind]) + .replyToLink(dataApps.map(a => NostrLink.fromEvent(a))) + .authors(followList); + } + + const dataRecommends = useRequestBuilder(reccomendsSub); + return { + reccomends: dataRecommends, + apps: dataApps, + }; +} diff --git a/packages/app/src/lang.json b/packages/app/src/lang.json index 3330a36e..7e90aba8 100644 --- a/packages/app/src/lang.json +++ b/packages/app/src/lang.json @@ -1106,6 +1106,9 @@ "TwyMau": { "defaultMessage": "Account" }, + "TzeMlV": { + "defaultMessage": "Sorry, we dont understand this event kind, please try one of the following apps instead!" + }, "U1aPPi": { "defaultMessage": "Stop listening" }, diff --git a/packages/app/src/translations/en.json b/packages/app/src/translations/en.json index 3af0a341..6ee76597 100644 --- a/packages/app/src/translations/en.json +++ b/packages/app/src/translations/en.json @@ -366,6 +366,7 @@ "Tpy00S": "People", "TvKqBp": "liked", "TwyMau": "Account", + "TzeMlV": "Sorry, we dont understand this event kind, please try one of the following apps instead!", "U1aPPi": "Stop listening", "UJTWqI": "Remove from my relays", "ULXFfP": "Receive", diff --git a/packages/system-wasm/Cargo.lock b/packages/system-wasm/Cargo.lock index a2eead3e..2a086694 100644 --- a/packages/system-wasm/Cargo.lock +++ b/packages/system-wasm/Cargo.lock @@ -346,9 +346,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -607,36 +607,36 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "secp256k1" -version = "0.28.0" +version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2acea373acb8c21ecb5a23741452acd2593ed44ee3d343e72baaa143bc89d0d5" +checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ "secp256k1-sys", ] [[package]] name = "secp256k1-sys" -version = "0.9.1" +version = "0.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dd97a086ec737e30053fd5c46f097465d25bb81dd3608825f65298c4c98be83" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" dependencies = [ "cc", ] [[package]] name = "serde" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" dependencies = [ "serde_derive", ] [[package]] name = "serde-wasm-bindgen" -version = "0.5.0" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +checksum = "8302e169f0eddcc139c70f139d19d6467353af16f9fce27e8c30158036a1e16b" dependencies = [ "js-sys", "serde", @@ -645,9 +645,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.193" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" dependencies = [ "proc-macro2", "quote", @@ -706,7 +706,7 @@ dependencies = [ "console_error_panic_hook", "criterion", "hex", - "itertools 0.11.0", + "itertools 0.13.0", "rand", "secp256k1", "serde", diff --git a/packages/system-wasm/Cargo.toml b/packages/system-wasm/Cargo.toml index 12b17ce1..9d0cc7c3 100644 --- a/packages/system-wasm/Cargo.toml +++ b/packages/system-wasm/Cargo.toml @@ -10,10 +10,10 @@ crate-type = ["cdylib", "rlib"] [dependencies] console_error_panic_hook = "0.1.7" hex = { version = "0.4.3", features = [], default-features = false } -itertools = "0.11.0" -secp256k1 = { version = "0.28.0", features = ["global-context"] } +itertools = "0.13.0" +secp256k1 = { version = "0.29.0", features = ["global-context"] } serde = { version = "1.0.188", features = ["derive"], default-features = false } -serde-wasm-bindgen = "0.5.0" +serde-wasm-bindgen = "0.6.5" serde_json = "1.0.105" sha256 = { version = "1.4.0", features = [], default-features = false } wasm-bindgen = "0.2.87" diff --git a/packages/system-wasm/pkg/system_wasm.d.ts b/packages/system-wasm/pkg/system_wasm.d.ts index e17b552f..8c023ce8 100644 --- a/packages/system-wasm/pkg/system_wasm.d.ts +++ b/packages/system-wasm/pkg/system_wasm.d.ts @@ -58,10 +58,10 @@ export interface InitOutput { readonly pow: (a: number, b: number, c: number) => void; readonly schnorr_verify: (a: number, b: number, c: number, d: number) => void; readonly schnorr_verify_event: (a: number, b: number) => void; - readonly rustsecp256k1_v0_9_1_context_create: (a: number) => number; - readonly rustsecp256k1_v0_9_1_context_destroy: (a: number) => void; - readonly rustsecp256k1_v0_9_1_default_illegal_callback_fn: (a: number, b: number) => void; - readonly rustsecp256k1_v0_9_1_default_error_callback_fn: (a: number, b: number) => void; + readonly rustsecp256k1_v0_10_0_context_create: (a: number) => number; + readonly rustsecp256k1_v0_10_0_context_destroy: (a: number) => void; + readonly rustsecp256k1_v0_10_0_default_illegal_callback_fn: (a: number, b: number) => void; + readonly rustsecp256k1_v0_10_0_default_error_callback_fn: (a: number, b: number) => void; readonly __wbindgen_malloc: (a: number, b: number) => number; readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number; readonly __wbindgen_add_to_stack_pointer: (a: number) => number; diff --git a/packages/system-wasm/pkg/system_wasm.js b/packages/system-wasm/pkg/system_wasm.js index 6542fa61..d1ce919b 100644 --- a/packages/system-wasm/pkg/system_wasm.js +++ b/packages/system-wasm/pkg/system_wasm.js @@ -463,6 +463,14 @@ function __wbg_get_imports() { const ret = new Error(getStringFromWasm0(arg0, arg1)); return addHeapObject(ret); }; + imports.wbg.__wbindgen_number_new = function (arg0) { + const ret = arg0; + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_string_new = function (arg0, arg1) { + const ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; imports.wbg.__wbindgen_jsval_loose_eq = function (arg0, arg1) { const ret = getObject(arg0) == getObject(arg1); return ret; @@ -478,23 +486,19 @@ function __wbg_get_imports() { getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret; getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret); }; - imports.wbg.__wbindgen_number_new = function (arg0) { - const ret = arg0; - return addHeapObject(ret); + imports.wbg.__wbindgen_as_number = function (arg0) { + const ret = +getObject(arg0); + return ret; }; imports.wbg.__wbindgen_object_clone_ref = function (arg0) { const ret = getObject(arg0); return addHeapObject(ret); }; - imports.wbg.__wbindgen_string_new = function (arg0, arg1) { - const ret = getStringFromWasm0(arg0, arg1); - return addHeapObject(ret); - }; - imports.wbg.__wbg_getwithrefkey_5e6d9547403deab8 = function (arg0, arg1) { + imports.wbg.__wbg_getwithrefkey_edc2c8960f0f1191 = function (arg0, arg1) { const ret = getObject(arg0)[getObject(arg1)]; return addHeapObject(ret); }; - imports.wbg.__wbg_set_841ac57cff3d672b = function (arg0, arg1, arg2) { + imports.wbg.__wbg_set_f975102236d3c502 = function (arg0, arg1, arg2) { getObject(arg0)[takeObject(arg1)] = takeObject(arg2); }; imports.wbg.__wbg_get_44be0491f933a435 = function (arg0, arg1) { diff --git a/packages/system-wasm/pkg/system_wasm_bg.wasm b/packages/system-wasm/pkg/system_wasm_bg.wasm index 6fae1c8f..069cb76d 100644 Binary files a/packages/system-wasm/pkg/system_wasm_bg.wasm and b/packages/system-wasm/pkg/system_wasm_bg.wasm differ diff --git a/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts b/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts index ca0c7e8e..8359dfe3 100644 --- a/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts +++ b/packages/system-wasm/pkg/system_wasm_bg.wasm.d.ts @@ -9,10 +9,10 @@ export function compress(a: number, b: number): void; export function pow(a: number, b: number, c: number): void; export function schnorr_verify(a: number, b: number, c: number, d: number): void; export function schnorr_verify_event(a: number, b: number): void; -export function rustsecp256k1_v0_9_1_context_create(a: number): number; -export function rustsecp256k1_v0_9_1_context_destroy(a: number): void; -export function rustsecp256k1_v0_9_1_default_illegal_callback_fn(a: number, b: number): void; -export function rustsecp256k1_v0_9_1_default_error_callback_fn(a: number, b: number): void; +export function rustsecp256k1_v0_10_0_context_create(a: number): number; +export function rustsecp256k1_v0_10_0_context_destroy(a: number): void; +export function rustsecp256k1_v0_10_0_default_illegal_callback_fn(a: number, b: number): void; +export function rustsecp256k1_v0_10_0_default_error_callback_fn(a: number, b: number): void; export function __wbindgen_malloc(a: number, b: number): number; export function __wbindgen_realloc(a: number, b: number, c: number, d: number): number; export function __wbindgen_add_to_stack_pointer(a: number): number; diff --git a/packages/system-wasm/src/filter.rs b/packages/system-wasm/src/filter.rs index 2292f193..b81b4ffd 100644 --- a/packages/system-wasm/src/filter.rs +++ b/packages/system-wasm/src/filter.rs @@ -34,6 +34,8 @@ pub struct ReqFilter { pub a_tag: Option>, #[serde(rename = "#g", skip_serializing_if = "Option::is_none")] pub g_tag: Option>, + #[serde(rename = "#k", skip_serializing_if = "Option::is_none")] + pub k_tag: Option>, #[serde(rename = "relays", skip_serializing_if = "Option::is_none")] pub relays: Option>, #[serde(rename = "search", skip_serializing_if = "Option::is_none")] @@ -75,6 +77,8 @@ pub struct FlatReqFilter { pub a_tag: Option, #[serde(rename = "#g", skip_serializing_if = "Option::is_none")] pub g_tag: Option, + #[serde(rename = "#k", skip_serializing_if = "Option::is_none")] + pub k_tag: Option, #[serde(rename = "search", skip_serializing_if = "Option::is_none")] pub search: Option, #[serde(rename = "relay", skip_serializing_if = "Option::is_none")] @@ -129,6 +133,7 @@ impl Distance for FlatReqFilter { ret += prop_dist(&self.r_tag, &b.r_tag); ret += prop_dist(&self.t_tag, &b.t_tag); ret += prop_dist(&self.g_tag, &b.g_tag); + ret += prop_dist(&self.k_tag, &b.k_tag); ret } @@ -163,6 +168,7 @@ impl From> for ReqFilter { array_prop_append(&x.r_tag, &mut acc.r_tag); array_prop_append(&x.a_tag, &mut acc.a_tag); array_prop_append(&x.g_tag, &mut acc.g_tag); + array_prop_append(&x.k_tag, &mut acc.k_tag); acc.search = x.search.to_owned(); acc.since = x.since; acc.until = x.until; @@ -188,6 +194,7 @@ impl From> for ReqFilter { array_prop_append_vec(&x.r_tag, &mut acc.r_tag); array_prop_append_vec(&x.a_tag, &mut acc.a_tag); array_prop_append_vec(&x.g_tag, &mut acc.g_tag); + array_prop_append_vec(&x.k_tag, &mut acc.k_tag); acc.search = x.search.to_owned(); acc.since = x.since; acc.until = x.until; @@ -281,6 +288,14 @@ impl Into> for &ReqFilter { inputs.push(t_ids); } + if let Some(k_tags) = &self.k_tag { + let t_ids = k_tags + .iter() + .map(|z| StringOrNumberEntry::String(("k_tag", z))) + .collect(); + inputs.push(t_ids); + } + for p in inputs.iter().multi_cartesian_product() { ret.push(FlatReqFilter { id: p.iter().find_map(|q| { @@ -371,6 +386,14 @@ impl Into> for &ReqFilter { } None }), + k_tag: p.iter().find_map(|q| { + if let StringOrNumberEntry::String((k, v)) = q { + if (*k).eq("k_tag") { + return Some((*v).to_string()); + } + } + None + }), search: self.search.to_owned(), since: self.since, until: self.until, @@ -395,6 +418,8 @@ impl Distance for ReqFilter { ret += prop_dist_vec(&self.r_tag, &b.r_tag); ret += prop_dist_vec(&self.t_tag, &b.t_tag); ret += prop_dist_vec(&self.a_tag, &b.a_tag); + ret += prop_dist_vec(&self.g_tag, &b.g_tag); + ret += prop_dist_vec(&self.k_tag, &b.k_tag); ret } diff --git a/packages/system/src/cache/index.ts b/packages/system/src/cache/index.ts index 3fbd3c1b..57691e99 100644 --- a/packages/system/src/cache/index.ts +++ b/packages/system/src/cache/index.ts @@ -57,7 +57,7 @@ export interface UsersFollows { } export function mapEventToProfile(ev: NostrEvent) { - if (ev.kind !== 0) return; + if (ev.kind !== 0 && ev.kind !== 31990) return; try { const data: UserMetadata = JSON.parse(ev.content); let ret = {