feat: support NIP22 for comment
This commit is contained in:
20
src-tauri/Cargo.lock
generated
20
src-tauri/Cargo.lock
generated
@@ -3093,7 +3093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if",
|
"cfg-if",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.48.5",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@@ -3492,7 +3492,7 @@ checksum = "0676bb32a98c1a483ce53e500a81ad9c3d5b3f7c920c28c24e9cb0980d0b5bc8"
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr"
|
name = "nostr"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"aes",
|
"aes",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
@@ -3523,7 +3523,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-connect"
|
name = "nostr-connect"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async-utility",
|
"async-utility",
|
||||||
@@ -3537,7 +3537,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-database"
|
name = "nostr-database"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"flatbuffers",
|
"flatbuffers",
|
||||||
@@ -3551,7 +3551,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-lmdb"
|
name = "nostr-lmdb"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"heed",
|
"heed",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3564,7 +3564,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-relay-pool"
|
name = "nostr-relay-pool"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"async-wsocket",
|
"async-wsocket",
|
||||||
@@ -3582,7 +3582,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-sdk"
|
name = "nostr-sdk"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"async-utility",
|
"async-utility",
|
||||||
"atomic-destructor",
|
"atomic-destructor",
|
||||||
@@ -3601,7 +3601,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nostr-zapper"
|
name = "nostr-zapper"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"nostr",
|
"nostr",
|
||||||
@@ -3746,7 +3746,7 @@ dependencies = [
|
|||||||
[[package]]
|
[[package]]
|
||||||
name = "nwc"
|
name = "nwc"
|
||||||
version = "0.36.0"
|
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 = [
|
dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"async-utility",
|
"async-utility",
|
||||||
@@ -7268,7 +7268,7 @@ version = "0.1.9"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.48.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|||||||
@@ -33,9 +33,6 @@ tauri-plugin-theme = "2.1.2"
|
|||||||
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
|
tauri-plugin-decorum = { git = "https://github.com/clearlysid/tauri-plugin-decorum" }
|
||||||
tauri-specta = { version = "2.0.0-rc.15", features = ["derive", "typescript"] }
|
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 = "^2.0.0-rc.20"
|
||||||
specta-typescript = "0.0.7"
|
specta-typescript = "0.0.7"
|
||||||
tokio = { version = "1", features = ["full"] }
|
tokio = { version = "1", features = ["full"] }
|
||||||
@@ -52,6 +49,13 @@ tracing-subscriber = { version = "0.3.18", features = ["fmt"] }
|
|||||||
async-trait = "0.1.83"
|
async-trait = "0.1.83"
|
||||||
webbrowser = "1.0.2"
|
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]
|
[target.'cfg(target_os = "macos")'.dependencies]
|
||||||
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
border = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
||||||
share-picker = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
share-picker = { git = "https://github.com/ahkohd/tauri-toolkit", branch = "v2" }
|
||||||
|
|||||||
@@ -71,11 +71,11 @@ pub async fn get_meta_from_event(content: String) -> Result<Meta, ()> {
|
|||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
pub async fn get_replies(id: String, state: State<'_, Nostr>) -> Result<Vec<RichEvent>, String> {
|
||||||
let client = &state.client;
|
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()
|
let filter = Filter::new()
|
||||||
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
|
.kind(Kind::Comment)
|
||||||
.event(event_id);
|
.custom_tag(SingleLetterTag::uppercase(Alphabet::E), [event_id]);
|
||||||
|
|
||||||
let mut events = Events::new(&[filter.clone()]);
|
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()),
|
Err(e) => return Err(e.to_string()),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Detect root event from reply
|
// Find root event from reply
|
||||||
let root_ids: Vec<&EventId> = reply_to
|
let root_tag = reply_to
|
||||||
.tags
|
.tags
|
||||||
.filter_standardized(TagKind::e())
|
.find(TagKind::SingleLetter(SingleLetterTag::uppercase(
|
||||||
.filter_map(|t| match t {
|
Alphabet::E,
|
||||||
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();
|
|
||||||
|
|
||||||
// Get root event if exist
|
// Get root event if exist
|
||||||
let root = match root_ids.first() {
|
let root = match root_tag {
|
||||||
Some(&id) => client
|
Some(tag) => match tag.content() {
|
||||||
.database()
|
Some(content) => {
|
||||||
.event_by_id(id)
|
let id = EventId::parse(content).map_err(|err| err.to_string())?;
|
||||||
.await
|
|
||||||
.map_err(|err| err.to_string())?,
|
client
|
||||||
|
.database()
|
||||||
|
.event_by_id(&id)
|
||||||
|
.await
|
||||||
|
.map_err(|err| err.to_string())?
|
||||||
|
}
|
||||||
|
None => None,
|
||||||
|
},
|
||||||
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)
|
.add_tags(tags)
|
||||||
.pow(DEFAULT_DIFFICULTY);
|
.pow(DEFAULT_DIFFICULTY);
|
||||||
|
|
||||||
|
|||||||
@@ -105,28 +105,27 @@ pub async fn create_column(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
} else if let Ok(event_id) = EventId::parse(&id) {
|
} 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 {
|
let subscription_id = SubscriptionId::new(webview.label());
|
||||||
tauri::async_runtime::spawn(async move {
|
|
||||||
let state = webview.state::<Nostr>();
|
|
||||||
let client = &state.client;
|
|
||||||
|
|
||||||
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()
|
if let Err(e) = client
|
||||||
.event(event_id)
|
.subscribe_with_id(subscription_id, vec![filter], None)
|
||||||
.kinds(vec![Kind::TextNote, Kind::Custom(1111)])
|
.await
|
||||||
.since(Timestamp::now());
|
{
|
||||||
|
println!("Subscription error: {}", e);
|
||||||
if let Err(e) = client
|
}
|
||||||
.subscribe_with_id(subscription_id, vec![filter], None)
|
});
|
||||||
.await
|
|
||||||
{
|
|
||||||
println!("Subscription error: {}", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,13 +56,6 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
// Get words
|
// Get words
|
||||||
let words: Vec<_> = content.split_whitespace().collect();
|
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
|
// Get hashtags
|
||||||
let hashtags = words
|
let hashtags = words
|
||||||
.iter()
|
.iter()
|
||||||
@@ -70,6 +63,13 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
.map(|&s| s.to_string().replace("#", "").to_lowercase())
|
.map(|&s| s.to_string().replace("#", "").to_lowercase())
|
||||||
.collect::<Vec<_>>();
|
.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 {
|
for mention in mentions {
|
||||||
let entity = mention.replace("nostr:", "").replace('@', "");
|
let entity = mention.replace("nostr:", "").replace('@', "");
|
||||||
|
|
||||||
@@ -92,8 +92,11 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
}
|
}
|
||||||
if entity.starts_with("note") {
|
if entity.starts_with("note") {
|
||||||
if let Ok(event_id) = EventId::from_bech32(&entity) {
|
if let Ok(event_id) = EventId::from_bech32(&entity) {
|
||||||
let hex = event_id.to_hex();
|
let tag = Tag::from_standardized(TagStandard::Quote {
|
||||||
let tag = Tag::parse(&["e", &hex, "", "mention"]).unwrap();
|
event_id,
|
||||||
|
relay_url: None,
|
||||||
|
public_key: None,
|
||||||
|
});
|
||||||
tags.push(tag);
|
tags.push(tag);
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
@@ -101,14 +104,12 @@ pub fn create_tags(content: &str) -> Vec<Tag> {
|
|||||||
}
|
}
|
||||||
if entity.starts_with("nevent") {
|
if entity.starts_with("nevent") {
|
||||||
if let Ok(event) = Nip19Event::from_bech32(&entity) {
|
if let Ok(event) = Nip19Event::from_bech32(&entity) {
|
||||||
let hex = event.event_id.to_hex();
|
let relay_url = event.relays.first().map(UncheckedUrl::from);
|
||||||
let relay = event.clone().relays.into_iter().next().unwrap_or("".into());
|
let tag = Tag::from_standardized(TagStandard::Quote {
|
||||||
let tag = Tag::parse(&["e", &hex, &relay, "mention"]).unwrap();
|
event_id: event.event_id,
|
||||||
|
relay_url,
|
||||||
if let Some(author) = event.author {
|
public_key: event.author,
|
||||||
let tag = Tag::public_key(author);
|
});
|
||||||
tags.push(tag);
|
|
||||||
}
|
|
||||||
|
|
||||||
tags.push(tag);
|
tags.push(tag);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -236,8 +236,8 @@ fn main() {
|
|||||||
// Config
|
// Config
|
||||||
let opts = Options::new()
|
let opts = Options::new()
|
||||||
.gossip(true)
|
.gossip(true)
|
||||||
.max_avg_latency(Duration::from_millis(500))
|
.max_avg_latency(Duration::from_secs(2))
|
||||||
.timeout(Duration::from_secs(5));
|
.timeout(Duration::from_secs(10));
|
||||||
|
|
||||||
// Setup nostr client
|
// Setup nostr client
|
||||||
let client = ClientBuilder::default()
|
let client = ClientBuilder::default()
|
||||||
@@ -532,7 +532,7 @@ fn main() {
|
|||||||
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
|
if let Err(e) = handle_clone.emit("metadata", event.as_json()) {
|
||||||
println!("Emit error: {}", e)
|
println!("Emit error: {}", e)
|
||||||
}
|
}
|
||||||
} else if event.kind == Kind::TextNote {
|
} else if event.kind == Kind::Comment {
|
||||||
let payload = RichEvent {
|
let payload = RichEvent {
|
||||||
raw: event.as_json(),
|
raw: event.as_json(),
|
||||||
parsed: if event.kind == Kind::TextNote {
|
parsed: if event.kind == Kind::TextNote {
|
||||||
@@ -544,7 +544,7 @@ fn main() {
|
|||||||
|
|
||||||
if let Err(e) = handle_clone.emit_to(
|
if let Err(e) = handle_clone.emit_to(
|
||||||
EventTarget::labeled(subscription_id.to_string()),
|
EventTarget::labeled(subscription_id.to_string()),
|
||||||
"event",
|
"comment",
|
||||||
payload,
|
payload,
|
||||||
) {
|
) {
|
||||||
println!("Emit error: {}", e)
|
println!("Emit error: {}", e)
|
||||||
|
|||||||
@@ -99,14 +99,9 @@ function ReplyList() {
|
|||||||
const res = await commands.getReplies(id);
|
const res = await commands.getReplies(id);
|
||||||
|
|
||||||
if (res.status === "ok") {
|
if (res.status === "ok") {
|
||||||
const events = res.data
|
const events = res.data.map((item) =>
|
||||||
// Create Lume Events
|
LumeEvent.from(item.raw, item.parsed),
|
||||||
.map((item) => LumeEvent.from(item.raw, item.parsed))
|
);
|
||||||
// Filter quote
|
|
||||||
.filter(
|
|
||||||
(ev) =>
|
|
||||||
!ev.tags.filter((t) => t[0] === "q" || t[3] === "mention").length,
|
|
||||||
);
|
|
||||||
|
|
||||||
return events;
|
return events;
|
||||||
} else {
|
} else {
|
||||||
@@ -179,7 +174,7 @@ function ReplyList() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const unlisten = getCurrentWindow().listen<EventPayload>(
|
const unlisten = getCurrentWindow().listen<EventPayload>(
|
||||||
"event",
|
"comment",
|
||||||
async (data) => {
|
async (data) => {
|
||||||
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
|
const event = LumeEvent.from(data.payload.raw, data.payload.parsed);
|
||||||
|
|
||||||
@@ -216,7 +211,7 @@ function ReplyList() {
|
|||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center gap-2">
|
<div className="flex items-center justify-center gap-2">
|
||||||
<Spinner className="size-4" />
|
<Spinner className="size-4" />
|
||||||
<span className="text-sm font-medium">Getting replies...</span>
|
<span className="text-sm font-medium">Loading replies...</span>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-col gap-3">
|
<div className="flex flex-col gap-3">
|
||||||
|
|||||||
@@ -1,9 +1,3 @@
|
|||||||
import { commands } from "@/commands.gen";
|
|
||||||
import { createFileRoute } from "@tanstack/react-router";
|
import { createFileRoute } from "@tanstack/react-router";
|
||||||
|
|
||||||
export const Route = createFileRoute("/columns/_layout/events/$id")({
|
export const Route = createFileRoute("/columns/_layout/events/$id")();
|
||||||
beforeLoad: async () => {
|
|
||||||
const accounts = await commands.getAccounts();
|
|
||||||
return { accounts };
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ export const Route = createLazyFileRoute("/new-post/")({
|
|||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const { reply_to } = Route.useSearch();
|
const { reply_to } = Route.useSearch();
|
||||||
const { accounts, initialValue, queryClient } = Route.useRouteContext();
|
const { accounts, initialValue } = Route.useRouteContext();
|
||||||
const { deferMentionList } = Route.useLoaderData();
|
const { deferMentionList } = Route.useLoaderData();
|
||||||
const users = useAwaited({ promise: deferMentionList })[0];
|
const users = useAwaited({ promise: deferMentionList })[0];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user