105 lines
3.9 KiB
Python
105 lines
3.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Nostr Comms Pipeline
|
|
Raw implementation - no nostr SDK needed.
|
|
Schnorr signing via coincurve, websockets via websockets library.
|
|
"""
|
|
import json, time, asyncio, secrets, hashlib, coincurve, websockets
|
|
|
|
def load_nsec():
|
|
with open("/root/nostr-relay/keystore.json") as f:
|
|
data = json.load(f)
|
|
return data.get("nostr", {}).get("secret", "")
|
|
|
|
def make_evt(hex_pub, hex_sec, kind, content, tags=None):
|
|
"""Create and sign a Nostr event using coincurve schnorr."""
|
|
tags = tags or []
|
|
ts = int(time.time())
|
|
serial = [0, hex_pub, ts, kind, tags, content]
|
|
evt_json = json.dumps(serial, separators=(',', ':'), ensure_ascii=False)
|
|
evt_id = hashlib.sha256(evt_json.encode()).hexdigest()
|
|
|
|
sk = coincurve.PrivateKey(bytes.fromhex(hex_sec))
|
|
sig = sk.sign_schnorr(bytes.fromhex(evt_id))
|
|
|
|
return {
|
|
"id": evt_id,
|
|
"pubkey": hex_pub,
|
|
"created_at": ts,
|
|
"kind": kind,
|
|
"tags": tags,
|
|
"content": content,
|
|
"sig": sig.hex()
|
|
}
|
|
|
|
async def post(relay, evt):
|
|
"""Post to relay with NIP-42 auth handshake."""
|
|
async with websockets.connect(relay) as ws:
|
|
await ws.send(json.dumps(["EVENT", evt]))
|
|
while True:
|
|
try:
|
|
raw = await asyncio.wait_for(ws.recv(), timeout=5)
|
|
resp = json.loads(raw)
|
|
if resp[0] == "AUTH":
|
|
challenge = resp[1]
|
|
auth_evt = make_evt(evt["pubkey"], load_nsec() or evt["sig"][:64], 22242, "", [
|
|
["relay", "relay.alexanderwhitestone.com:2929"],
|
|
["challenge", challenge]
|
|
])
|
|
await ws.send(json.dumps(["AUTH", auth_evt["id"]]))
|
|
await ws.send(json.dumps(["EVENT", evt]))
|
|
continue
|
|
if resp[0] == "OK":
|
|
return resp[2] is True, resp[3] if len(resp) > 3 else ""
|
|
print(f" {resp[0]}")
|
|
except asyncio.TimeoutError:
|
|
return True, "timeout"
|
|
|
|
async def main():
|
|
# Get keypair from keystore
|
|
sec_hex = load_nsec()
|
|
if not sec_hex:
|
|
with open("/Users/apayne/.timmy/nostr/agent_keys.json") as f:
|
|
keys = json.load(f)
|
|
sec_hex = keys["timmy"]["hex_sec"]
|
|
hex_pub = keys["timmy"]["hex_pub"]
|
|
|
|
code = secrets.token_hex(4)
|
|
print(f"Group code: {code}\n")
|
|
|
|
print("1. Creating group metadata (kind 39000)")
|
|
group_content = json.dumps({"name": "Timmy Time", "about": "The Timmy Foundation household"}, separators=(',', ':'))
|
|
tags = [["d", code], ["name", "Timmy Time"], ["pubkey", hex_pub]]
|
|
evt = make_evt(hex_pub, sec_hex, 39000, group_content, tags)
|
|
ok, msg = await post("ws://127.0.0.1:2929", evt)
|
|
print(f" OK={ok} {msg}\n")
|
|
|
|
print("2. Test message (kind 1)")
|
|
evt = make_evt(hex_pub, sec_hex, 1, "Timmy speaks: Nostr comms pipeline operational.")
|
|
ok, msg = await post("ws://127.0.0.1:2929", evt)
|
|
print(f" OK={ok} {msg}\n")
|
|
|
|
print("3. Group chat (kind 9)")
|
|
evt = make_evt(hex_pub, sec_hex, 9, "Welcome to Timmy Time household group.", [["h", code]])
|
|
ok, msg = await post("ws://127.0.0.1:2929", evt)
|
|
print(f" OK={ok} {msg}\n")
|
|
|
|
print("4. Morning report (kind 1)")
|
|
report = (
|
|
"TIMMY MORNING REPORT\n"
|
|
f"Tick: 260 | Evennia healthy | 8 agents active\n"
|
|
f"Nostr: operational | Group: {code}\n"
|
|
"Sovereignty and service always."
|
|
)
|
|
evt = make_evt(hex_pub, sec_hex, 1, report)
|
|
ok, msg = await post("ws://127.0.0.1:2929", evt)
|
|
print(f" OK={ok} {msg}")
|
|
|
|
cfg = {"relay": "wss://relay.alexanderwhitestone.com:2929", "group_code": code,
|
|
"created": time.strftime("%Y-%m-%d %H:%M:%S")}
|
|
with open("/root/nostr-relay/group_config.json", "w") as f:
|
|
json.dump(cfg, f, indent=2)
|
|
print(f"\nConfig saved. Group: {code}")
|
|
|
|
asyncio.run(main())
|