dtonon / nostr-ruby

A ruby library to interact with the Nostr Protocol
MIT License
47 stars 7 forks source link
nostr nostr-protocol ruby

Nostr Ruby

A ruby library to interact with the Nostr Protocol.

[!Warning] This version in work in progress and breaks the v0.2.0 API

Installation

Add this line to your application's Gemfile:

# Gemfile
gem 'nostr_ruby'

And then execute:

$ bundle

Or install it yourself as:

$ gem install nostr_ruby

Usage

Manage keys

require "nostr_ruby"

sk = Nostr::Key.generate_private_key
# => "8090fb3fe26e27d539ee349d70890d338c5e2e8b459e04c8e97658f03d2f9f33"

pk = Nostr::Key.get_public_key(sk)
# => "e7ded9bd42e7c74fcc6465962b919b7efcd5774ac6bea2ae6b81b2caa9d4d2e6"

Decode entities


puplic_key = Nostr::Bech32.decode(npub)
# => {:hrp=>"npub", :data=>"e7ded9bd42e7c74fcc6465962b919b7efcd5774ac6bea2ae6b81b2caa9d4d2e6"}

nprofile_data = Nostr::Bech32.decode("nprofile1qqs8hhhhhc3dmrje73squpz255ape7t448w86f7ltqemca7m0p99spgprpmhxue69uhkgar0dehkutnwdaehgu339e3k7mf06ras84")
# => {:hrp=>"nprofile", :data=>{:pubkey=>["7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805"], :relay=>["wss://dtonon.nostr1.com/"]}}

note_data = Nostr::Bech32.decode("note1xzce08egncw3mcm8l8edas6rrhgfj9l5uwwv2hz03zym0m9eg5hsxuyajp")
# => {:hrp=>"note", :data=>"30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f"}

nevent_data = Nostr::Bech32.decode("nevent1qqsrpvvhnu5fu8gaudnlnuk7cdp3m5yezl6w88x9t38c3zdhaju52tcpzpmhxue69uhkztnwdaejumr0dshsz9nhwden5te0vfjhvmewdehhxarjxyhxxmmd9uq3wamnwvaz7tmzd96xxmmfdejhytnnda3kjctv9ulrdeva")
# => {:hrp=>"nevent", :data=> {:id=>["30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f"], :relay=>["wss://a.nos.lol/", "wss://bevo.nostr1.com/", "wss://bitcoiner.social/"]}}

naddr_data = Nostr::Bech32.decode("naddr1qvzqqqr4gupzq77777lz9hvwt86xqrsyf2jn588ewk5aclf8mavr80rhmduy5kq9qqdkc6t5w3kx2ttvdamx2ttxdaez6mr0denj6en0wfkkzaqxq5r99")
# => => {:hrp=>"naddr", :data=>{:kind=>[30023], :author=>["7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805"], :identifier=>["little-love-for-long-format"]}}

Encode entities


nsec = Nostr::Bech32.encode_nsec(sk)
# => "nsec1szg0k0lzdcna2w0wxjwhpzgdxwx9ut5tgk0qfj8fwev0q0f0nuessml5ur"

npub = Nostr::Bech32.encode_npub(pk)
# => "npub1ul0dn02zulr5lnryvktzhyvm0m7d2a62c6l29tntsxev42w56tnqksrtfu"

nprofile = Nostr::Bech32.encode_nprofile(
  pubkey: "7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805",
  relays: ["wss://dtonon.nostr1.com"],
)
# => "nprofile1qqs8hhhhhc3dmrje73squpz255ape7t448w86f7ltqemca7m0p99spgpzamhxue69uhkgar0dehkutnwdaehgu339e3k7mg60me8x"

note = Nostr::Bech32.encode_note("30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f")
# => "note1xzce08egncw3mcm8l8edas6rrhgfj9l5uwwv2hz03zym0m9eg5hsxuyajp"

nevent = Nostr::Bech32.encode_nevent(
  id: "30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f",
  relays: ["wss://nos.lol"],
)
# => "nevent1qqsrpvvhnu5fu8gaudnlnuk7cdp3m5yezl6w88x9t38c3zdhaju52tcpp4mhxue69uhkummn9ekx7mqrqsqqqqqpux7e9q"

naddr = Nostr::Bech32.encode_naddr(
  author: "7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805",
  identifier: "little-love-for-long-format",
  kind: 30023
)
# => "naddr1qgs8hhhhhc3dmrje73squpz255ape7t448w86f7ltqemca7m0p99spgrqsqqqa28qqdkc6t5w3kx2ttvdamx2ttxdaez6mr0denj6en0wfkkzaqn2tjdj"

Initialize Client

require "nostr_ruby"

# Detailed version
s = Nostr::Signer.new(private_key: Nostr::Key.generate_private_key)
c = Nostr::Client.new(signer: s)

# Compact version, under the hood Client creates a Signer
c = Nostr::Client.new(private_key: Nostr::Key.generate_private_key)

c.private_key
# => "7402b4b1ee09fb37b64ec2a958f1b7815d904c6dd44227bdef7912ef201af97d"

c.public_key
# => "a19f3c16b6e857d2b673c67eea293431fc175895513ca2f687a717152a5da466"

c.nsec
# => "nsec1wsptfv0wp8an0djwc2543udhs9weqnrd63pz00000yfw7gq6l97snckpdq"

c.npub
# => "npub15x0nc94kapta9dnncelw52f5x87pwky42y729a585ut322ja53nq72yrcr"

Create, sign and send an event

# Initialize a client, under the hood Client creates a Signer
c = Nostr::Client.new(
  private_key: "7402b4b1ee09fb37b64ec2a958f1b7815d904c6dd44227bdef7912ef201af97d",
  relay: "wss://nos.lol"
)

# Initialize an event
e = Nostr::Event.new(
  kind: ...,
  pubkey: ...,
  created_at: ...,
  tags: ...,
  content: ...,
  pow: ...,
  delegation: ...,
  recipient: ...,
)

# Sign the event
e = c.sign(e)

# - - - - - - - - - - - - - -
# Full async mode

# Set the open callback
c.on :connect do |event|
  puts 'Publish event...'
  c.publish(e)
end

# Set the response callback
c.on :message do |event|
  puts "Response: #{event}"
end

# Connect and send the event
c.connect
c.publish(e)
c.stop

# - - - - - - - - - - - - - -
# Compact sync mode

c.connect
c.publish_and_wait(e)
c.stop

Set the profile

metadata = {
  name: "Mr Robot",
  about: "I walk around the city",
  picture: "https://upload.wikimedia.org/wikipedia/commons/3/35/Mr_robot_photo.jpg",
  nip05: "mrrobot@mrrobot.com"
}

e = Nostr::Event.new(
  kind: Nostr::Kind::METADATA,
  pubkey: c.public_key,
  content: metadata.to_json,
)

Post a note

e = Nostr::Event.new(
  kind: Nostr::Kind::SHORT_NOTE,
  pubkey: c.public_key,
  content: "Hello Nostr!",
)

Share a contact list

contact_list = [
  ["54399b6d8200813bfc53177ad4f13d6ab712b6b23f91aefbf5da45aeb5c96b08", "wss://alicerelay.com/", "alice"],
  ["850708b7099215bf9a1356d242c2354939e9a844c1359d3b5209592a0b420452", "wss://bobrelay.com/nostr", "bob"],
  ["f7f4b0072368460a09138bf3966fb1c59d0bdadfc3aff4e59e6896194594a82a", "ws://carolrelay.com/ws", "carol"]
]

e = Nostr::Event.new(
  kind: Nostr::Kind::CONTACT_LIST,
  pubkey: c.public_key,
  tags: contact_list.map { |c| ['p'] + c },
)

Delete an event

event_to_delete = "b91b3fb40128112c38dc54168b9f601c22bf8fcae6e70bb2a5f53e7f3ae44388"
e = Nostr::Event.new(
  kind: Nostr::Kind::DELETION,
  pubkey: c.public_key,
  tags: [["e", event_to_delete]],
)

React to an event

e = Nostr::Event.new(
  kind: Nostr::Kind::REACTION,
  pubkey: c.public_key,
  content: "+",
  tags: [["e", target_event]],
)

# You can also use emoji
e2 = Nostr::Event.new(
  kind: Nostr::Kind::REACTION,
  pubkey: c.public_key,
  content: "🔥",
  tags: [["e", target_event]],
)

Create events with a PoW difficulty

# Just add the `pow` argument 
e = Nostr::Event.new(
  kind: Nostr::Kind::SHORT_NOTE,
  pubkey: c.public_key,
  content: "Hello Nostr!",
  pow: 15,
)

Create an event with a NIP-26 delegation

delegator = Nostr::Client.new(private_key: delegator_key)

delegatee = "b1d8dfd69fe8795042dbbc4d3f85938a01d4740c54d2daf11088c75c50ff19d9"
conditions = "kind=1&created_at>#{Time.now.to_i}&created_at<#{(Time.now + 60*60).to_i}"
delegation_tag = delegator.generate_delegation_tag(
  to: delegatee,
  conditions: conditions
)

# The `delegation_tag` is given to the delegatee so it can use it

delegatee = Nostr::Client.new(private_key: delegatee_key)

e = Nostr::Event.new(
  kind: Nostr::Kind::SHORT_NOTE,
  pubkey: delegatee.public_key,
  content: "Hello Nostr!",
  delegation: delegation_tag,
)

delegatee.sign(e)

Send a direct message

Warning: This uses NIP-04, that will be deprecated in favor of NIP-17

recipient = "npub1ul0dn02zulr5lnryvktzhyvm0m7d2a62c6l29tntsxev42w56tnqksrtfu"

e = Nostr::Event.new(
  kind: Nostr::Kind::DIRECT_MESSAGE,
  pubkey: c.public_key,
  recipient: Nostr::Bech32.decode(recipient)[:data],
  content: "Hello Alice!"
)

e = Nostr::Event.new(
  kind: Nostr::Kind::DIRECT_MESSAGE,
  pubkey: c.public_key,
  tags: [["p", Nostr::Bech32.decode(recipient)[:data]]],
  content: "Hello Alice!"
)

Decrypt a direct message

Warning: This uses NIP-04, that will be deprecated in favor of NIP-17

payload = {
  :kind=>4,
  :pubkey=>"a19f3c16b6e857d2b673c67eea293431fc175895513ca2f687a717152a5da466",
  :created_at=>1725387307,
  :tags=>[["p", "e7ded9bd42e7c74fcc6465962b919b7efcd5774ac6bea2ae6b81b2caa9d4d2e6"]],
  :content=>"Nd7n/wId1oiprUCC4WWwNw==?iv=7gIRExcyO1xystretLIPnQ==",
  :id=>"b91b3fb40128112c38dc54168b9f601c22bf8fcae6e70bb2a5f53e7f3ae44388",
  :sig=>"73edf5a6acbefdd3d76f28ba90faaabe348a24c798f8fa33797eec29e2404c33a455815a59472ecd023441df38d815f83d81b95b8cb2f2c88a52982c8f7301e9"
}
e = Nostr::Event.new(**payload)

c.decrypt(e)
puts e.content
=> "Hello Alice!"

Create a subscribtion to receive events

filter = Nostr::Filter.new(
  kinds: [1],
  authors: ["a19f3c16b6e857d2b673c67eea293431fc175895513ca2f687a717152a5da466"],
  since: Time.now - (60*60*24), # 24 hours ago
  limit: 10
)
c.on :message do |message|
  puts ">> #{message}"
end
c.subscribe(filter: filter)
c.connect