Files
timmy-home/nostr/post_raw.py

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())