buzzkit 0.1.0__tar.gz → 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {buzzkit-0.1.0 → buzzkit-0.1.1}/Cargo.lock +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/Cargo.toml +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/PKG-INFO +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/examples/claim_invite.py +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/examples/list_channels.py +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/examples/offline_sign.py +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/examples/send_message.py +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/examples/set_profile.py +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/examples/subscribe.py +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/pyproject.toml +1 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/python/buzzkit/__init__.py +5 -1
- {buzzkit-0.1.0 → buzzkit-0.1.1}/python/buzzkit/_native.pyi +4 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/python/buzzkit/client.py +11 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/src/lib.rs +22 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/tests/test_signing.py +12 -3
- {buzzkit-0.1.0 → buzzkit-0.1.1}/.github/workflows/CI.yml +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/.github/workflows/release.yml +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/.gitignore +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/LICENSE +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/LICENSE-APACHE +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/NOTICE +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/README.md +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/examples/_shared.py +0 -0
- {buzzkit-0.1.0 → buzzkit-0.1.1}/python/buzzkit/py.typed +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Redeem a relay invite so the agent becomes a community member.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
BUZZ_RELAY_URL=wss://... python examples/claim_invite.py <invite_url_or_code>
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Post a message to a channel via the HTTP bridge.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
BUZZ_RELAY_URL=wss://... python examples/send_message.py <channel-uuid> ["message"]
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Publish the agent's profile (kind 0) so it shows a display name in Buzz.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
BUZZ_RELAY_URL=wss://... python examples/set_profile.py ["Display Name"]
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""Real-time inbound: subscribe to a channel over the WebSocket and print messages.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
BUZZ_RELAY_URL=wss://... python examples/subscribe.py <channel-uuid>
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
from __future__ import annotations
|
|
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "buzzkit"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.1"
|
|
8
8
|
description = "Python bindings + async client for Block's Buzz (Nostr) protocol, backed by Rust buzz-core/buzz-sdk"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.12"
|
|
@@ -10,6 +10,7 @@ This is an independent, unofficial project — not affiliated with Block, Inc.
|
|
|
10
10
|
from __future__ import annotations
|
|
11
11
|
|
|
12
12
|
from ._native import (
|
|
13
|
+
KIND_ADD_MEMBER,
|
|
13
14
|
KIND_AUTH,
|
|
14
15
|
KIND_HTTP_AUTH,
|
|
15
16
|
KIND_PRESENCE_UPDATE,
|
|
@@ -17,6 +18,7 @@ from ._native import (
|
|
|
17
18
|
KIND_STREAM_MESSAGE,
|
|
18
19
|
KIND_STREAM_MESSAGE_V2,
|
|
19
20
|
build_auth_event,
|
|
21
|
+
build_join_channel_event,
|
|
20
22
|
build_message_event,
|
|
21
23
|
build_profile_event,
|
|
22
24
|
generate_keypair,
|
|
@@ -26,9 +28,10 @@ from ._native import (
|
|
|
26
28
|
)
|
|
27
29
|
from .client import BuzzClient
|
|
28
30
|
|
|
29
|
-
__version__ = "0.1.
|
|
31
|
+
__version__ = "0.1.1"
|
|
30
32
|
|
|
31
33
|
__all__ = [
|
|
34
|
+
"KIND_ADD_MEMBER",
|
|
32
35
|
"KIND_AUTH",
|
|
33
36
|
"KIND_HTTP_AUTH",
|
|
34
37
|
"KIND_PRESENCE_UPDATE",
|
|
@@ -37,6 +40,7 @@ __all__ = [
|
|
|
37
40
|
"KIND_STREAM_MESSAGE_V2",
|
|
38
41
|
"BuzzClient",
|
|
39
42
|
"build_auth_event",
|
|
43
|
+
"build_join_channel_event",
|
|
40
44
|
"build_message_event",
|
|
41
45
|
"build_profile_event",
|
|
42
46
|
"generate_keypair",
|
|
@@ -21,6 +21,9 @@ def build_profile_event(
|
|
|
21
21
|
) -> str:
|
|
22
22
|
"""Build + sign a profile event (kind 0); returns NIP-01 event JSON."""
|
|
23
23
|
|
|
24
|
+
def build_join_channel_event(secret: str, channel_id: str) -> str:
|
|
25
|
+
"""Build + sign a NIP-29 channel self-join event (kind 9000, role=bot)."""
|
|
26
|
+
|
|
24
27
|
def build_auth_event(secret: str, challenge: str, relay_url: str) -> str:
|
|
25
28
|
"""Build + sign a NIP-42 AUTH event (kind 22242); returns event JSON."""
|
|
26
29
|
|
|
@@ -36,3 +39,4 @@ KIND_PRESENCE_UPDATE: int
|
|
|
36
39
|
KIND_AUTH: int
|
|
37
40
|
KIND_HTTP_AUTH: int
|
|
38
41
|
KIND_STREAM_MESSAGE_V2: int
|
|
42
|
+
KIND_ADD_MEMBER: int
|
|
@@ -108,6 +108,17 @@ class BuzzClient:
|
|
|
108
108
|
)
|
|
109
109
|
return await self.post_event(ev)
|
|
110
110
|
|
|
111
|
+
async def join_channel(self, channel_id: str) -> dict:
|
|
112
|
+
"""Self-add as a bot member of a channel (NIP-29 kind 9000).
|
|
113
|
+
|
|
114
|
+
Published over the WebSocket — NIP-29 management events are not accepted
|
|
115
|
+
on the HTTP bridge — so :meth:`connect` must be called first. Required
|
|
116
|
+
for the identity's messages to reach other channel members and for it to
|
|
117
|
+
appear in mention autocomplete.
|
|
118
|
+
"""
|
|
119
|
+
ev = _native.build_join_channel_event(self._secret, channel_id)
|
|
120
|
+
return await self.publish(ev)
|
|
121
|
+
|
|
111
122
|
async def query(self, filters: list[dict]) -> list[dict]:
|
|
112
123
|
"""Run a NIP-01 REQ over the HTTP bridge; returns a list of events."""
|
|
113
124
|
url = f"{self._http}/query"
|
|
@@ -140,6 +140,26 @@ fn build_profile_event(
|
|
|
140
140
|
Ok(event.as_json())
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
/// Build and sign a NIP-29 channel self-join event (kind 9000, role=bot).
|
|
144
|
+
/// The signer adds itself (`p` = own pubkey) to `channel_id`'s member list.
|
|
145
|
+
#[pyfunction]
|
|
146
|
+
fn build_join_channel_event(secret: &str, channel_id: &str) -> PyResult<String> {
|
|
147
|
+
let keys = keys_from_secret(secret)?;
|
|
148
|
+
let cid = Uuid::parse_str(channel_id)
|
|
149
|
+
.map_err(|e| PyValueError::new_err(format!("channel_id must be a UUID: {e}")))?;
|
|
150
|
+
let pubkey_hex = keys.public_key().to_hex();
|
|
151
|
+
// The `p` tag references the signer itself (self-join), so we must opt into
|
|
152
|
+
// self-tagging — nostr's EventBuilder strips author-matching `p` tags by
|
|
153
|
+
// default, which the relay would then reject as "missing p tag".
|
|
154
|
+
let builder = buzz_sdk::build_add_member(cid, &pubkey_hex, Some(buzz_sdk::MemberRole::Bot))
|
|
155
|
+
.map_err(|e| PyValueError::new_err(format!("build_add_member: {e}")))?
|
|
156
|
+
.allow_self_tagging();
|
|
157
|
+
let event = builder
|
|
158
|
+
.sign_with_keys(&keys)
|
|
159
|
+
.map_err(|e| PyValueError::new_err(format!("sign: {e}")))?;
|
|
160
|
+
Ok(event.as_json())
|
|
161
|
+
}
|
|
162
|
+
|
|
143
163
|
#[pymodule]
|
|
144
164
|
fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
145
165
|
m.add_function(wrap_pyfunction!(generate_keypair, m)?)?;
|
|
@@ -149,6 +169,7 @@ fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
149
169
|
m.add_function(wrap_pyfunction!(sign_nip98, m)?)?;
|
|
150
170
|
m.add_function(wrap_pyfunction!(verify_event, m)?)?;
|
|
151
171
|
m.add_function(wrap_pyfunction!(build_profile_event, m)?)?;
|
|
172
|
+
m.add_function(wrap_pyfunction!(build_join_channel_event, m)?)?;
|
|
152
173
|
|
|
153
174
|
// Buzz event kinds (subset — mirrors buzz-core/src/kind.rs).
|
|
154
175
|
m.add("KIND_REACTION", 7u16)?;
|
|
@@ -157,5 +178,6 @@ fn _native(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
|
157
178
|
m.add("KIND_AUTH", 22242u16)?;
|
|
158
179
|
m.add("KIND_HTTP_AUTH", 27235u16)?;
|
|
159
180
|
m.add("KIND_STREAM_MESSAGE_V2", 40002u16)?;
|
|
181
|
+
m.add("KIND_ADD_MEMBER", 9000u16)?;
|
|
160
182
|
Ok(())
|
|
161
183
|
}
|
|
@@ -59,8 +59,17 @@ def test_nip98_header_shape():
|
|
|
59
59
|
|
|
60
60
|
def test_auth_event_is_kind_22242():
|
|
61
61
|
nsec, _, _ = buzzkit.generate_keypair()
|
|
62
|
-
event = json.loads(
|
|
63
|
-
buzzkit.build_auth_event(nsec, "challenge-123", "wss://relay.example")
|
|
64
|
-
)
|
|
62
|
+
event = json.loads(buzzkit.build_auth_event(nsec, "challenge-123", "wss://relay.example"))
|
|
65
63
|
assert event["kind"] == buzzkit.KIND_AUTH == 22242
|
|
66
64
|
assert ["challenge", "challenge-123"] in event["tags"]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_join_channel_event_keeps_self_p_tag():
|
|
68
|
+
nsec, _, pk_hex = buzzkit.generate_keypair()
|
|
69
|
+
event = json.loads(buzzkit.build_join_channel_event(nsec, str(uuid.uuid4())))
|
|
70
|
+
assert event["kind"] == buzzkit.KIND_ADD_MEMBER == 9000
|
|
71
|
+
# The `p` tag references the signer itself; it must survive nostr's self-tag
|
|
72
|
+
# stripping (allow_self_tagging) or the relay rejects the join as "missing p tag".
|
|
73
|
+
assert ["p", pk_hex] in event["tags"]
|
|
74
|
+
assert ["role", "bot"] in event["tags"]
|
|
75
|
+
assert any(t[0] == "h" for t in event["tags"])
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|