Files
client/app-stacks/publish_test_stack.py
T

139 lines
6.5 KiB
Python

#!/usr/bin/env python3
"""
Publish a test App Stack (kind 30267) for "Nostr Clients" using n_signer via qrexec.
Usage:
python3 app-stacks/publish_test_stack.py
"""
import asyncio
import json
import struct
import subprocess
import sys
import time
import websockets
QREXEC_TARGET = "nostr_signer"
QREXEC_SERVICE = "qubes.NsignerRpc"
RELAY = "wss://relay.zapstore.dev"
def qrexec_call(request):
"""Send a framed JSON-RPC request via qrexec and return the response."""
framed = struct.pack(">I", len(json.dumps(request).encode())) + json.dumps(request).encode()
proc = subprocess.run(
["qrexec-client-vm", QREXEC_TARGET, QREXEC_SERVICE],
input=framed, capture_output=True, timeout=30
)
if proc.returncode != 0:
raise Exception(f"qrexec failed: {proc.stderr.decode()}")
resp_len = struct.unpack(">I", proc.stdout[:4])[0]
return json.loads(proc.stdout[4:4 + resp_len])
async def publish_event(signed_event):
"""Publish a signed event to the relay."""
async with websockets.connect(RELAY, max_size=10_000_000, open_timeout=10) as ws:
msg = json.dumps(["EVENT", signed_event])
await ws.send(msg)
while True:
try:
resp = await asyncio.wait_for(ws.recv(), timeout=10)
data = json.loads(resp)
if data[0] == "OK" and data[1] == signed_event["id"]:
accepted = data[2]
msg = data[3] if len(data) > 3 else ""
print(f" Relay: {'✅ ACCEPTED' if accepted else '❌ REJECTED'}: {msg}")
return accepted
except asyncio.TimeoutError:
print(" No OK response from relay")
return False
async def main():
print("=" * 60)
print("Publishing App Stack: Nostr Clients")
print("=" * 60)
# Step 1: Get pubkey for nostr_index 0
print("\n1. Getting pubkey from n_signer...")
req = {"id": "1", "method": "nostr_get_public_key", "params": [{"nostr_index": 0}]}
resp = qrexec_call(req)
if "error" in resp:
print(f" Error: {resp['error']}")
sys.exit(1)
pubkey = resp["result"]
print(f" Pubkey: {pubkey}")
# Step 2: Build the unsigned event
created_at = int(time.time())
event = {
"kind": 30267,
"content": "",
"tags": [
["d", "privacy-approved-nostr-clients"],
["t", "app-stack"],
["name", "Privacy Approved — Nostr Clients"],
["description", "Nostr-native social and communication clients that passed privacy review. No tracking SDKs, minimal permissions, open source."],
["f", "android-arm64-v8a"],
["h", ""],
["a", "32267:aa9047325603dacd4f8142093567973566de3b1e20a89557b728c3be4c6a844b:com.vitorpamplona.amethyst", "wss://relay.zapstore.dev"],
["a", "32267:78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d:com.greenart7c3.nostrsigner", "wss://relay.zapstore.dev"],
["a", "32267:e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb:com.wisp.app", "wss://relay.zapstore.dev"],
["a", "32267:e2ccf7cf20403f3f2a4a55b328f0de3be38558a7d5f33632fdaaefc726c1c8eb:com.darkwisp.app", "wss://relay.zapstore.dev"],
["a", "32267:75d737c3472471029c44876b330d2284288a42779b591a2ed4daa1c6c07efaf7:org.parres.whitenoise", "wss://relay.zapstore.dev"],
["a", "32267:f55678aa1f5d554536d456b13beab04f636d63fdedd586fe38a4cb9ce48c90bc:com.nospeak.app", "wss://relay.zapstore.dev"],
["a", "32267:b2cdcb37d32533145c00c4f43d5e1e1deb7c67bceea7ef63f526ca4cab891633:org.nostr.nostrord", "wss://relay.zapstore.dev"],
["a", "32267:85df00a2f6a91845354c8d2d9fbab4002bb85b4225baeab60fafb2587c5038ea:com.shosho.app", "wss://relay.zapstore.dev"],
["a", "32267:7d33ba57d8a6e8869a1f1d5215254597594ac0dbfeb01b690def8c461b82db35:com.traveltelly.app", "wss://relay.zapstore.dev"],
["a", "32267:b22b06b051fd5232966a9344a634d956c3dc33a7f5ecdcad9ed11ddc4120a7f2:app.nostrmail.client", "wss://relay.zapstore.dev"],
["a", "32267:c4a39f1291291d452405cd8ddd798c4a29a3858c52cd0d843f1f6852cf17682e:co.openvine.app", "wss://relay.zapstore.dev"],
["a", "32267:20986fb83e775d96d188ca5c9df10ce6d613e0eb7e5768a0f0b12b37cdac21b3:com.yakihonne.yakihonne", "wss://relay.zapstore.dev"],
["a", "32267:3f770d65d3a764a9c5cb503ae123e62ec7598ad035d836e2a810f3877a745b24:com.nostrnests.app", "wss://relay.zapstore.dev"],
["a", "32267:d1bd33333733dcc411f0ee893b38b8522fc0de227fff459d99044ced9e65581b:app.nostria.twa", "wss://relay.zapstore.dev"],
["a", "32267:d1bd33333733dcc411f0ee893b38b8522fc0de227fff459d99044ced9e65581b:app.nostria", "wss://relay.zapstore.dev"],
["a", "32267:78ce6faa72264387284e647ba6938995735ec8c7d5c5a65737e55130f026307d:dev.zapstore.alpha", "wss://relay.zapstore.dev"],
["a", "32267:d70b722b67768cbf1e3ac777e2107bbc447a9768899b548bf694f71a2a6b52f8:com.pearcal", "wss://relay.zapstore.dev"],
["a", "32267:d70b722b67768cbf1e3ac777e2107bbc447a9768899b548bf694f71a2a6b52f8:com.pearcircle", "wss://relay.zapstore.dev"],
["a", "32267:781a1527055f74c1f70230f10384609b34548f8ab6a0a6caa74025827f9fdae5:pub.ditto.app", "wss://relay.zapstore.dev"],
["a", "32267:97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322:social.flotilla", "wss://relay.zapstore.dev"],
],
"created_at": created_at,
"pubkey": pubkey,
}
print(f"\n2. Signing event (kind 30267, d=privacy-approved-nostr-clients)...")
req = {
"id": "2",
"method": "nostr_sign_event",
"params": [json.dumps(event), {"nostr_index": 0}]
}
resp = qrexec_call(req)
if "error" in resp:
print(f" Error: {resp['error']}")
sys.exit(1)
signed_event = json.loads(resp["result"])
print(f" Event ID: {signed_event['id']}")
print(f" Signature: {signed_event['sig'][:20]}...")
# Step 3: Publish to relay
print(f"\n3. Publishing to {RELAY}...")
accepted = await publish_event(signed_event)
if accepted:
print(f"\n✅ Published successfully!")
print(f" Stack: privacy-approved-nostr-clients")
print(f" Apps: 19 Nostr clients")
else:
print(f"\n❌ Failed to publish")
# Save the signed event
with open("app-stacks/signed_stack_event.json", "w") as f:
json.dump(signed_event, f, indent=2)
print(f"\n Signed event saved to app-stacks/signed_stack_event.json")
if __name__ == "__main__":
asyncio.run(main())