first
commit
f056ed800a
@ -0,0 +1,4 @@
|
||||
config*
|
||||
*.json
|
||||
target
|
||||
*.lock
|
@ -0,0 +1,26 @@
|
||||
[package]
|
||||
name = "nor"
|
||||
description = "nostr client in rust"
|
||||
version = "0.0.1"
|
||||
authors = ["syui <syui@syui.cf>"]
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
seahorse = "*"
|
||||
dotenv = "0.15"
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
serde = "*"
|
||||
config = { git = "https://github.com/mehcode/config-rs", branch = "master" }
|
||||
shellexpand = "*"
|
||||
curl = "*"
|
||||
image = "*"
|
||||
os_type = "2.4"
|
||||
toml = "*"
|
||||
futures = "0.3.5"
|
||||
url = { version = "2.0", features = ["serde"] }
|
||||
anyhow = "1"
|
||||
nostr = "*"
|
||||
tungstenite = { version = "0.17", features = ["rustls-tls-webpki-roots"]}
|
||||
nostr-sdk = "*"
|
||||
tokio = { version = "*", features = ["full"] }
|
@ -0,0 +1,7 @@
|
||||
- https://docs.rs/nostr/latest/nostr/
|
||||
|
||||
- https://docs.rs/nostr-sdk/latest/nostr_sdk/
|
||||
|
||||
```sh
|
||||
$ cargo build
|
||||
```
|
@ -0,0 +1,27 @@
|
||||
use config::{Config, ConfigError, File};
|
||||
use serde_derive::Deserialize;
|
||||
use std::borrow::Cow;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[allow(unused)]
|
||||
pub struct Data {
|
||||
pub name: Cow<'static, str>,
|
||||
pub secret_key: Cow<'static, str>,
|
||||
pub display_name: Cow<'static, str>,
|
||||
pub about: Cow<'static, str>,
|
||||
pub picture: Cow<'static, str>,
|
||||
pub banner: Cow<'static, str>,
|
||||
pub nip: Cow<'static, str>,
|
||||
pub lud: Cow<'static, str>,
|
||||
}
|
||||
|
||||
impl Data {
|
||||
pub fn new() -> Result<Self, ConfigError> {
|
||||
let d = shellexpand::tilde("~") + "/.config/nor/config.toml";
|
||||
let s = Config::builder()
|
||||
.add_source(File::with_name(&d))
|
||||
.add_source(config::Environment::with_prefix("APP"))
|
||||
.build()?;
|
||||
s.try_deserialize()
|
||||
}
|
||||
}
|
@ -0,0 +1,129 @@
|
||||
pub mod data;
|
||||
use std::env;
|
||||
use url::Url;
|
||||
use data::Data as Datas;
|
||||
use seahorse::{App, Command, Context};
|
||||
use nostr::{Event, EventBuilder, Metadata};
|
||||
use nostr::key::{FromBech32, Keys};
|
||||
use nostr::message::ClientMessage;
|
||||
use tungstenite::{connect, Message as WsMessage};
|
||||
const WS_ENDPOINT: &str = "wss://relay.damus.io";
|
||||
use nostr_sdk::{Client};
|
||||
use nostr::SubscriptionFilter;
|
||||
use nostr::util::time::timestamp;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
let app = App::new(env!("CARGO_PKG_NAME"))
|
||||
.author(env!("CARGO_PKG_AUTHORS"))
|
||||
.description(env!("CARGO_PKG_DESCRIPTION"))
|
||||
.version(env!("CARGO_PKG_VERSION"))
|
||||
.usage("nor [option] [x]")
|
||||
.command(
|
||||
Command::new("timeline")
|
||||
.usage("nor t")
|
||||
.description("timeline")
|
||||
.alias("t")
|
||||
.action(t),
|
||||
)
|
||||
.command(
|
||||
Command::new("notify")
|
||||
.usage("nor n")
|
||||
.description("notify")
|
||||
.alias("n")
|
||||
.action(n),
|
||||
)
|
||||
.command(
|
||||
Command::new("status")
|
||||
.usage("nor s")
|
||||
.description("status")
|
||||
.alias("s")
|
||||
.action(s),
|
||||
)
|
||||
.command(
|
||||
Command::new("post")
|
||||
.usage("nor p {}")
|
||||
.description("post message, ex: $ nor p $text")
|
||||
.alias("p")
|
||||
.action(p),
|
||||
)
|
||||
;
|
||||
app.run(args);
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn timeline() -> anyhow::Result<()> {
|
||||
let data = Datas::new().unwrap();
|
||||
let my_keys = Keys::from_bech32(&data.secret_key)?;
|
||||
let mut client = Client::new(&my_keys);
|
||||
client
|
||||
.add_relay("wss://relay.damus.io", None)
|
||||
.unwrap();
|
||||
client.connect_relay("wss://relay.damus.io", true).await.unwrap();
|
||||
client.connect().await?;
|
||||
let subscription = SubscriptionFilter::new()
|
||||
.pubkeys(vec![my_keys.public_key()])
|
||||
.since(timestamp());
|
||||
|
||||
let t = client.get_events_of(vec![subscription]).await.unwrap();
|
||||
println!("{:#?}", t);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn t(_c: &Context) {
|
||||
timeline().unwrap();
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn notify() -> anyhow::Result<()> {
|
||||
let data = Datas::new().unwrap();
|
||||
let my_keys = Keys::from_bech32(&data.secret_key)?;
|
||||
let client = Client::new(&my_keys);
|
||||
let notifications = client.notifications();
|
||||
println!("{:?}", notifications);
|
||||
Ok(())
|
||||
|
||||
//loop {
|
||||
// let mut notifications = client.notifications();
|
||||
// while let Ok(notification) = notifications.recv().await {
|
||||
// println!("{:?}", notification);
|
||||
// }
|
||||
//}
|
||||
}
|
||||
|
||||
fn n(_c: &Context) {
|
||||
notify().unwrap();
|
||||
}
|
||||
|
||||
fn status() -> anyhow::Result<()> {
|
||||
let data = Datas::new().unwrap();
|
||||
let metadata = Metadata::new()
|
||||
.name(data.name)
|
||||
.display_name(data.display_name)
|
||||
.about(data.about)
|
||||
.picture(Url::parse(&data.picture)?)
|
||||
.nip05(data.nip);
|
||||
let my_keys = Keys::from_bech32(&data.secret_key)?;
|
||||
let event: Event = EventBuilder::set_metadata(&my_keys, metadata)?.to_event(&my_keys)?;
|
||||
println!("{:#?}", event);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn s(_c: &Context) {
|
||||
status().unwrap();
|
||||
}
|
||||
|
||||
fn post(c: &Context) -> anyhow::Result<()> {
|
||||
let text = c.args[0].to_string();
|
||||
let data = Datas::new().unwrap();
|
||||
let my_keys = Keys::from_bech32(&data.secret_key)?;
|
||||
let event: Event = EventBuilder::new_text_note(&text, &[]).to_event(&my_keys)?;
|
||||
let (mut socket, _response) = connect(WS_ENDPOINT).expect("Can't connect to relay");
|
||||
let msg = ClientMessage::new_event(event).to_json();
|
||||
socket.write_message(WsMessage::Text(msg))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn p(c: &Context) {
|
||||
post(c).unwrap();
|
||||
}
|
Loading…
Reference in New Issue