feat: support NIP22 for comment

This commit is contained in:
reya 2024-11-10 08:51:07 +07:00
parent ca20bbd298
commit 2bcda1f2ef
9 changed files with 86 additions and 101 deletions

20
src-tauri/Cargo.lock generated
View File

@ -3093,7 +3093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
dependencies = [
"cfg-if",
"windows-targets 0.52.6",
"windows-targets 0.48.5",
]
[[package]]
@ -3492,7 +3492,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
[[package]]
name = "nostr"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"aes",
"async-trait",
@ -3523,7 +3523,7 @@ dependencies = [
[[package]]
name = "nostr-connect"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"async-trait",
"async-utility",
@ -3537,7 +3537,7 @@ dependencies = [
[[package]]
name = "nostr-database"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"async-trait",
"flatbuffers",
@ -3551,7 +3551,7 @@ dependencies = [
[[package]]
name = "nostr-lmdb"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"heed",
"nostr",
@ -3564,7 +3564,7 @@ dependencies = [
[[package]]
name = "nostr-relay-pool"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"async-utility",
"async-wsocket",
@ -3582,7 +3582,7 @@ dependencies = [
[[package]]
name = "nostr-sdk"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"async-utility",
"atomic-destructor",
@ -3601,7 +3601,7 @@ dependencies = [
[[package]]
name = "nostr-zapper"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"async-trait",
"nostr",
@ -3746,7 +3746,7 @@ dependencies = [
[[package]]
name = "nwc"
version = "0.36.0"
source = "git+https://github.com/rust-nostr/nostr#46d96391d94316d6bf1637e10f1b980f866f1879"
source = "git+https://github.com/reyamir/nostr?branch=feat/nip-22#9afd230b93494390438c69fbd6f2a7de79fa0af8"
dependencies = [
"async-trait",
"async-utility",
@ -7268,7 +7268,7 @@ version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [
"windows-sys 0.59.0",
"windows-sys 0.48.0",
]
[[package]]

View File

@ -33,9 +33,6 @@ tauri-plugin-theme = "2.1.2"
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
specta = "^2.0.0-rc.20"
specta-typescript = "0.0.7"
tokio = { version = "1", features = ["full"] }
@ -52,6 +49,13 @@ tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
async-trait = "0.1.83"
webbrowser = "1.0.2"
nostr-sdk = { git = "https://github.com/rust-nostr/nostr", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/rust-nostr/nostr" }
[patch.'https://github.com/rust-nostr/nostr']
nostr-sdk = { git = "https://github.com/reyamir/nostr", branch = "feat/nip-22", features = ["lmdb", "webln", "all-nips"] }
nostr-connect = { git = "https://github.com/reyamir/nostr", branch = "feat/nip-22" }
[target.'cfg(target_os = "macos")'.dependencies]
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
share-picker = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }

View File

@ -71,11 +71,11 @@ pub async fn get_meta_from_event(content: String) -> Result<Meta, ()> {
#[specta::specta]
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
let client = &state.client;
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
let event_id = EventId::parse(&id).map_err(|err| err.to_string())?;
let filter = Filter::new()
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
.event(event_id);
.kind(Kind::Comment)
.custom_tag(SingleLetterTag::uppercase(Alphabet::E), [event_id]);
let mut events = Events::new(&[filter.clone()]);
@ -523,39 +523,31 @@ pub async fn reply(content: String, to: String, state: State<'_, Nostr>) -> Resu
Err(e) => return Err(e.to_string()),
};
// Detect root event from reply
let root_ids: Vec<&EventId> = reply_to
// Find root event from reply
let root_tag = reply_to
.tags
.filter_standardized(TagKind::e())
.filter_map(|t| match t {
TagStandard::Event {
event_id, marker, ..
} => {
if let Some(mkr) = marker {
match mkr {
Marker::Root => Some(event_id),
Marker::Reply => Some(event_id),
_ => None,
}
} else {
Some(event_id)
}
}
_ => None,
})
.collect();
.find(TagKind::SingleLetter(SingleLetterTag::uppercase(
Alphabet::E,
)));
// Get root event if exist
let root = match root_ids.first() {
Some(&id) => client
.database()
.event_by_id(id)
.await
.map_err(|err| err.to_string())?,
let root = match root_tag {
Some(tag) => match tag.content() {
Some(content) => {
let id = EventId::parse(content).map_err(|err| err.to_string())?;
client
.database()
.event_by_id(&id)
.await
.map_err(|err| err.to_string())?
}
None => None,
},
None => None,
};
let builder = EventBuilder::text_note_reply(content, &reply_to, root.as_ref(), None)
let builder = EventBuilder::comment(content, &reply_to, root.as_ref(), None)
.add_tags(tags)
.pow(DEFAULT_DIFFICULTY);

View File

@ -105,28 +105,27 @@ pub async fn create_column(
});
}
} else if let Ok(event_id) = EventId::parse(&id) {
let is_thread = payload.url().to_string().contains("events");
tauri::async_runtime::spawn(async move {
let state = webview.state::<Nostr>();
let client = &state.client;
if is_thread {
tauri::async_runtime::spawn(async move {
let state = webview.state::<Nostr>();
let client = &state.client;
let subscription_id = SubscriptionId::new(webview.label());
let subscription_id = SubscriptionId::new(webview.label());
let filter = Filter::new()
.custom_tag(
SingleLetterTag::uppercase(Alphabet::E),
[event_id],
)
.kind(Kind::Comment)
.since(Timestamp::now());
let filter = Filter::new()
.event(event_id)
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
.since(Timestamp::now());
if let Err(e) = client
.subscribe_with_id(subscription_id, vec![filter], None)
.await
{
println!("Subscription error: {}", e);
}
});
}
if let Err(e) = client
.subscribe_with_id(subscription_id, vec![filter], None)
.await
{
println!("Subscription error: {}", e);
}
});
}
}
}

View File

@ -56,13 +56,6 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
// Get words
let words: Vec<_> = content.split_whitespace().collect();
// Get mentions
let mentions = words
.iter()
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
.map(|&s| s.to_string())
.collect::<Vec<_>>();
// Get hashtags
let hashtags = words
.iter()
@ -70,6 +63,13 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
.map(|&s| s.to_string().replace("#", "").to_lowercase())
.collect::<Vec<_>>();
// Get mentions
let mentions = words
.iter()
.filter(|&&word| ["nostr:", "@"].iter().any(|&el| word.starts_with(el)))
.map(|&s| s.to_string())
.collect::<Vec<_>>();
for mention in mentions {
let entity = mention.replace("nostr:", "").replace('@', "");
@ -92,8 +92,11 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
}
if entity.starts_with("note") {
if let Ok(event_id) = EventId::from_bech32(&entity) {
let hex = event_id.to_hex();
let tag = Tag::parse(&["e", &hex, "", "mention"]).unwrap();
let tag = Tag::from_standardized(TagStandard::Quote {
event_id,
relay_url: None,
public_key: None,
});
tags.push(tag);
} else {
continue;
@ -101,14 +104,12 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
}
if entity.starts_with("nevent") {
if let Ok(event) = Nip19Event::from_bech32(&entity) {
let hex = event.event_id.to_hex();
let relay = event.clone().relays.into_iter().next().unwrap_or("".into());
let tag = Tag::parse(&["e", &hex, &relay, "mention"]).unwrap();
if let Some(author) = event.author {
let tag = Tag::public_key(author);
tags.push(tag);
}
let relay_url = event.relays.first().map(UncheckedUrl::from);
let tag = Tag::from_standardized(TagStandard::Quote {
event_id: event.event_id,
relay_url,
public_key: event.author,
});
tags.push(tag);
} else {

View File

@ -236,8 +236,8 @@ fn main() {
// Config
let opts = Options::new()
.gossip(true)
.max_avg_latency(Duration::from_millis(500))
.timeout(Duration::from_secs(5));
.max_avg_latency(Duration::from_secs(2))
.timeout(Duration::from_secs(10));
// Setup nostr client
let client = ClientBuilder::default()
@ -532,7 +532,7 @@ fn main() {
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
println!("Emit error: {}", e)
}
} else if event.kind == Kind::TextNote {
} else if event.kind == Kind::Comment {
let payload = RichEvent {
raw: event.as_json(),
parsed: if event.kind == Kind::TextNote {
@ -544,7 +544,7 @@ fn main() {
if let Err(e) = handle_clone.emit_to(
EventTarget::labeled(subscription_id.to_string()),
"event",
"comment",
payload,
) {
println!("Emit error: {}", e)

View File

@ -99,14 +99,9 @@ function ReplyList() {
const res = await commands.getReplies(id);
if (res.status === "ok") {
const events = res.data
// Create Lume Events
.map((item) => LumeEvent.from(item.raw, item.parsed))
// Filter quote
.filter(
(ev) =>
!ev.tags.filter((t) => t[0] === "q" || t[3] === "mention").length,
);
const events = res.data.map((item) =>
LumeEvent.from(item.raw, item.parsed),
);
return events;
} else {
@ -179,7 +174,7 @@ function ReplyList() {
useEffect(() => {
const unlisten = getCurrentWindow().listen<EventPayload>(
"event",
"comment",
async (data) => {
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
@ -216,7 +211,7 @@ function ReplyList() {
{isLoading ? (
<div className="flex items-center justify-center gap-2">
<Spinner className="size-4" />
<span className="text-sm font-medium">Getting replies...</span>
<span className="text-sm font-medium">Loading replies...</span>
</div>
) : (
<div className="flex flex-col gap-3">

View File

@ -1,9 +1,3 @@
import { commands } from "@/commands.gen";
import { createFileRoute } from "@tanstack/react-router";
export const Route = createFileRoute("/columns/_layout/events/$id")({
beforeLoad: async () => {
const accounts = await commands.getAccounts();
return { accounts };
},
});
export const Route = createFileRoute("/columns/_layout/events/$id")();

View File

@ -70,7 +70,7 @@ export const Route = createLazyFileRoute("/new-post/")({
function Screen() {
const { reply_to } = Route.useSearch();
const { accounts, initialValue, queryClient } = Route.useRouteContext();
const { accounts, initialValue } = Route.useRouteContext();
const { deferMentionList } = Route.useLoaderData();
const users = useAwaited({ promise: deferMentionList })[0];