Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/monty888/monstr
core nostr from nostrpy seperated out from nostrpy and start to clean up and add examples WIP
https://github.com/monty888/monstr
nostr python
Last synced: 24 days ago
JSON representation
core nostr from nostrpy seperated out from nostrpy and start to clean up and add examples WIP
- Host: GitHub
- URL: https://github.com/monty888/monstr
- Owner: monty888
- License: mit
- Created: 2022-12-19T18:50:04.000Z (almost 2 years ago)
- Default Branch: master
- Last Pushed: 2024-04-19T16:22:35.000Z (8 months ago)
- Last Synced: 2024-04-19T17:39:12.033Z (8 months ago)
- Topics: nostr, python
- Language: Python
- Homepage:
- Size: 416 KB
- Stars: 27
- Watchers: 2
- Forks: 12
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
- awesome-nostr - monstr - python code for working with nostr (Relays / Implementations)
README
# monstr
Monstr: Python Nostr module. Python code for working with nostr.
* A basic relay implementation that can be used for testing, and can be easily extended.
* Client and ClientPool classes to manage access to one or multiple relays
* Keys for working with and converting between hex/npub/nsec
* KeyStore for keys load, access by alias store encrypted using NIP49, NIP44 or custom
* Signer classes for abstacting use of keys so for example signing could be done via hardware
* NIP46 NIP46ServerConnection to sign for remote client and NIP46Signer to use a remote signer
* Entities for encoding and decoding NIP19 nostr entities
* NIP4 and NIP44 implemented for payload encryption
* NIP59 gift wrapped events and old non standard gift wraps with inbox class# install
```sh
git clone https://github.com/monty888/monstr.git
cd monstr
python3 -m venv venv
source venv/bin/activate
pip install .
# probably required to run examples else monstr module won't be found
export PYTHONPATH="$PYTHONPATH:./"
```
to use postgres as store psycopg2 must be installed
```sh
# install wheel helper, if needed.
pip pip install wheel
# maybe required on linux
# sudo apt install postgresql automake pkg-config libtool
# maybe required on mac
# brew install postgresql automake pkg-config libtool libffi
# now actually install psycopg2
pip install psycopg2
```Note: developed against python 3.10.12
# use
### keys
```python
from monstr.encrypt import Keys# generate new keys
k = Keys()# import existing keys, where key_str is nsec, npub or hex - assumed public
k = Keys.get_key(key_str)# import existing hex private key
k = Keys(priv_k=key_str)```
### keystore
```python
import asyncio
from monstr.ident.keystore import SQLiteKeyStoreasync def get_store():
# keys store plain text see /examples/key_store.py to see how to password protect
store = SQLiteKeyStore('keystore.db')
nk = await store.get('monty')
# will be None if monty is not in the store
print(nk)if __name__ == '__main__':
asyncio.run(get_store())
```### run local relay
```python
import asyncio
import logging
from monstr.relay.relay import Relayasync def run_relay():
r = Relay()
await r.start()if __name__ == '__main__':
logging.getLogger().setLevel(logging.DEBUG)
asyncio.run(run_relay())
```
**_NOTE:_** By default this relay will be running at ws://localhost:8080 and not storing events### make a post
The following shows code to post note to the above local relay. Normally you'd use a ClientPool
rather than Client because it's normal to post to multiple relays. It should be possible to switch between
Client/ClientPool without any other changes in most cases.
The code shows:
- basic note post
- NIP4 encrypt post or NIP44 with code change as comment
- basic note post using signer class
```python
import asyncio
import logging
from monstr.client.client import Client, ClientPool
from monstr.encrypt import Keys, NIP4Encrypt
from monstr.event.event import Event
from monstr.signing.signing import BasicKeySignerasync def do_post(url, text):
# rnd generate some keys
n_keys = Keys()async with Client(url) as c:
# basic kind one note
n_msg = Event(kind=Event.KIND_TEXT_NOTE,
content=text,
pub_key=n_keys.public_key_hex())
n_msg.sign(n_keys.private_key_hex())
c.publish(n_msg)
# to encrypt in needs to be for someone, use these keys
to_k = Keys('nsec1znc5uy6e342rzn420l38q892qzmkvjz0hn836hhn8hl8wmkc670qp0lk9n')
# kind 4 for nip4, nip44 has no set kind so will depend
n_msg.kind = Event.KIND_ENCRYPT
# same nip4 encrypted
my_enc = NIP4Encrypt(n_keys) # or NIP44Encrypt(n_keys)
# returns event we to_p_tag and content encrypted
n_msg = my_enc.encrypt_event(evt=n_msg,
to_pub_k=to_k)n_msg.sign(n_keys.private_key_hex())
c.publish(n_msg)# or using signer send text post - better this way
my_signer = BasicKeySigner(key=Keys())n_msg = await my_signer.ready_post(Event(kind=Event.KIND_TEXT_NOTE,
content=text))
c.publish(n_msg)
# await asyncio.sleep(1)if __name__ == "__main__":
logging.getLogger().setLevel(logging.DEBUG)
url = "ws://localhost:8080"
text = 'hello'asyncio.run(do_post(url, text))
```### query
basic one time query to above relay
```python
import logging
import asyncio
from monstr.client.client import Client, ClientPool# default relay if not otherwise given
DEFAULT_RELAY = 'ws://localhost:8080'
FILTER = [{
'limit': 100
}]async def one_off_query_client_with(relay=DEFAULT_RELAY):
# does a one off query to relay prints the events and exits
async with Client(relay) as c:
events = await c.query(FILTER)
for c_evt in events:
print(c_evt)if __name__ == "__main__":
logging.getLogger().setLevel(logging.DEBUG)
asyncio.run(one_off_query_client_with())
```### subscription
Listen to posts being made to the local relay above```python
import asyncio
import logging
import sys
from monstr.client.client import Client, ClientPool
import signal
from monstr.encrypt import Keys
from monstr.event.event import Event
from monstr.util import util_funcstail = util_funcs.str_tails
async def listen_notes(url):
run = True# so we get a clean exit on ctrl-c
def sigint_handler(signal, frame):
nonlocal run
run = False
signal.signal(signal.SIGINT, sigint_handler)# create the client and start it running
c = Client(url)
asyncio.create_task(c.run())
await c.wait_connect()# just use func, you can also use a class that has a do_event
# with this method sig, e.g. extend monstr.client.EventHandler
def my_handler(the_client: Client, sub_id: str, evt: Event):
print(evt.created_at, tail(evt.content,30))# start listening for events
c.subscribe(handlers=my_handler,
filters={
'limit': 100
})while run:
await asyncio.sleep(0.1)if __name__ == "__main__":
logging.getLogger().setLevel(logging.DEBUG)
url = "ws://localhost:8080"asyncio.run(listen_notes(url))
```### NIP19 Entities
```python
from monstr.entities import Entitiesdef show_entities():
# nip19 encoded profile
n_profile = 'nprofile1qqsrhuxx8l9ex335q7he0f09aej04zpazpl0ne2cgukyawd24mayt8gpp4mhxue69uhhytnc9e3k7mgpz4mhxue69uhkg6nzv9ejuumpv34kytnrdaksjlyr9p'# extract data
decoded = Entities.decode(n_profile)
print(decoded)# re-encode
print(Entities.encode('nprofile', decoded))if __name__ == "__main__":
show_entities()
```further examples are in the /examples directory
# Contribute:
-- TODO