mirror of
https://github.com/lumehq/lume.git
synced 2025-03-17 13:22:05 +01:00
feat: support NIP22 for comment
This commit is contained in:
parent
ca20bbd298
commit
2bcda1f2ef
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"
|
||||
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]]
|
||||
|
@ -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" }
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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">
|
||||
|
@ -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")();
|
||||
|
@ -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];
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user