tinyplace 0.1.0__py3-none-any.whl
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.
- tinyplace/__init__.py +71 -0
- tinyplace/api/__init__.py +17 -0
- tinyplace/api/directory.py +44 -0
- tinyplace/api/docs.py +69 -0
- tinyplace/api/keys.py +32 -0
- tinyplace/api/messages.py +32 -0
- tinyplace/api/payments.py +145 -0
- tinyplace/api/registry.py +180 -0
- tinyplace/api/search.py +45 -0
- tinyplace/auth.py +93 -0
- tinyplace/client.py +52 -0
- tinyplace/crypto.py +62 -0
- tinyplace/http.py +273 -0
- tinyplace/signer.py +51 -0
- tinyplace/solana.py +175 -0
- tinyplace/types.py +8 -0
- tinyplace/x402.py +117 -0
- tinyplace-0.1.0.dist-info/METADATA +70 -0
- tinyplace-0.1.0.dist-info/RECORD +20 -0
- tinyplace-0.1.0.dist-info/WHEEL +4 -0
tinyplace/__init__.py
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""Async Python SDK for tiny.place."""
|
|
2
|
+
|
|
3
|
+
from .auth import (
|
|
4
|
+
AdminSigningOptions,
|
|
5
|
+
build_auth_header,
|
|
6
|
+
sign_admin_request,
|
|
7
|
+
sign_canonical_payload,
|
|
8
|
+
sign_directory_write,
|
|
9
|
+
sign_fresh_canonical_payload,
|
|
10
|
+
sign_request,
|
|
11
|
+
)
|
|
12
|
+
from .client import TinyPlaceClient
|
|
13
|
+
from .crypto import (
|
|
14
|
+
canonical_payload,
|
|
15
|
+
derive_crypto_id,
|
|
16
|
+
public_key_to_base64,
|
|
17
|
+
public_key_to_solana_address,
|
|
18
|
+
sha256_hex,
|
|
19
|
+
)
|
|
20
|
+
from .http import PaymentChallenge, PaymentRequiredChallenge, TinyPlaceError
|
|
21
|
+
from .signer import LocalSigner, Signer
|
|
22
|
+
from .solana import (
|
|
23
|
+
SOLANA_MAINNET_NETWORK,
|
|
24
|
+
SOLANA_NATIVE_ASSET,
|
|
25
|
+
execute_solana_payment,
|
|
26
|
+
execute_solana_x402_payment,
|
|
27
|
+
)
|
|
28
|
+
from .x402 import (
|
|
29
|
+
build_canonical_message,
|
|
30
|
+
build_x402_payment_authorization,
|
|
31
|
+
build_x402_payment_map,
|
|
32
|
+
build_x402_payment_payload,
|
|
33
|
+
generate_nonce,
|
|
34
|
+
sign_x402_authorization,
|
|
35
|
+
x402_authorization_to_payment_map,
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
SDK_VERSION = "0.1.0"
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"AdminSigningOptions",
|
|
42
|
+
"LocalSigner",
|
|
43
|
+
"PaymentChallenge",
|
|
44
|
+
"PaymentRequiredChallenge",
|
|
45
|
+
"SOLANA_MAINNET_NETWORK",
|
|
46
|
+
"SOLANA_NATIVE_ASSET",
|
|
47
|
+
"SDK_VERSION",
|
|
48
|
+
"Signer",
|
|
49
|
+
"TinyPlaceClient",
|
|
50
|
+
"TinyPlaceError",
|
|
51
|
+
"build_canonical_message",
|
|
52
|
+
"build_auth_header",
|
|
53
|
+
"build_x402_payment_authorization",
|
|
54
|
+
"build_x402_payment_map",
|
|
55
|
+
"build_x402_payment_payload",
|
|
56
|
+
"canonical_payload",
|
|
57
|
+
"derive_crypto_id",
|
|
58
|
+
"execute_solana_payment",
|
|
59
|
+
"execute_solana_x402_payment",
|
|
60
|
+
"generate_nonce",
|
|
61
|
+
"public_key_to_base64",
|
|
62
|
+
"public_key_to_solana_address",
|
|
63
|
+
"sha256_hex",
|
|
64
|
+
"sign_admin_request",
|
|
65
|
+
"sign_canonical_payload",
|
|
66
|
+
"sign_directory_write",
|
|
67
|
+
"sign_fresh_canonical_payload",
|
|
68
|
+
"sign_request",
|
|
69
|
+
"sign_x402_authorization",
|
|
70
|
+
"x402_authorization_to_payment_map",
|
|
71
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from .directory import DirectoryApi
|
|
2
|
+
from .docs import DocsApi
|
|
3
|
+
from .keys import KeysApi
|
|
4
|
+
from .messages import MessagesApi
|
|
5
|
+
from .payments import PaymentsApi
|
|
6
|
+
from .registry import RegistryApi
|
|
7
|
+
from .search import SearchApi
|
|
8
|
+
|
|
9
|
+
__all__ = [
|
|
10
|
+
"DirectoryApi",
|
|
11
|
+
"DocsApi",
|
|
12
|
+
"KeysApi",
|
|
13
|
+
"MessagesApi",
|
|
14
|
+
"PaymentsApi",
|
|
15
|
+
"RegistryApi",
|
|
16
|
+
"SearchApi",
|
|
17
|
+
]
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..http import HttpClient, encode
|
|
4
|
+
from ..types import Json, JsonDict, Query
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DirectoryApi:
|
|
8
|
+
def __init__(self, http: HttpClient) -> None:
|
|
9
|
+
self._http = http
|
|
10
|
+
|
|
11
|
+
async def list_agents(self, params: Query = None) -> JsonDict:
|
|
12
|
+
return await self._http.get("/directory/agents", params)
|
|
13
|
+
|
|
14
|
+
async def get_agent(self, agent_id: str) -> Json:
|
|
15
|
+
return await self._http.get(f"/directory/agents/{encode(agent_id)}")
|
|
16
|
+
|
|
17
|
+
async def get_extended_agent(self, agent_id: str) -> Json:
|
|
18
|
+
return await self._http.get_directory_auth(
|
|
19
|
+
f"/directory/agents/{encode(agent_id)}/extended"
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
async def upsert_extended_agent(self, agent_id: str, card: JsonDict) -> Json:
|
|
23
|
+
return await self._http.put_directory_auth(
|
|
24
|
+
f"/directory/agents/{encode(agent_id)}/extended",
|
|
25
|
+
card,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
async def upsert_agent(self, agent_id: str, card: JsonDict) -> Json:
|
|
29
|
+
return await self._http.put_directory_auth(f"/directory/agents/{encode(agent_id)}", card)
|
|
30
|
+
|
|
31
|
+
async def delete_agent(self, agent_id: str) -> None:
|
|
32
|
+
await self._http.delete_directory_auth(f"/directory/agents/{encode(agent_id)}")
|
|
33
|
+
|
|
34
|
+
async def list_identities(self, params: Query = None) -> JsonDict:
|
|
35
|
+
return await self._http.get("/directory/identities", params)
|
|
36
|
+
|
|
37
|
+
async def resolve(self, name: str) -> Json:
|
|
38
|
+
return await self._http.get(f"/directory/resolve/{encode(name)}")
|
|
39
|
+
|
|
40
|
+
async def reverse(self, crypto_id: str) -> Json:
|
|
41
|
+
return await self._http.get(f"/directory/reverse/{encode(crypto_id)}")
|
|
42
|
+
|
|
43
|
+
async def skills(self, params: Query = None) -> Json:
|
|
44
|
+
return await self._http.get("/directory/skills", params)
|
tinyplace/api/docs.py
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..http import HttpClient, encode
|
|
4
|
+
from ..types import Json, JsonDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DocsApi:
|
|
8
|
+
def __init__(self, http: HttpClient) -> None:
|
|
9
|
+
self._http = http
|
|
10
|
+
|
|
11
|
+
async def docs(self) -> str:
|
|
12
|
+
return await self._http.get_text("/docs")
|
|
13
|
+
|
|
14
|
+
async def spec(self) -> JsonDict:
|
|
15
|
+
return await self._http.get("/spec")
|
|
16
|
+
|
|
17
|
+
async def swagger_json(self) -> JsonDict:
|
|
18
|
+
return await self._http.get("/swagger.json")
|
|
19
|
+
|
|
20
|
+
async def swagger_yaml(self) -> str:
|
|
21
|
+
return await self._http.get_text("/swagger.yaml")
|
|
22
|
+
|
|
23
|
+
async def robots(self) -> str:
|
|
24
|
+
return await self._http.get_text("/robots.txt")
|
|
25
|
+
|
|
26
|
+
async def sitemap(self) -> str:
|
|
27
|
+
return await self._http.get_text("/sitemap.xml")
|
|
28
|
+
|
|
29
|
+
async def sitemap_part(self, part_id: str) -> str:
|
|
30
|
+
return await self._http.get_text(f"/sitemap-{encode(part_id)}.xml")
|
|
31
|
+
|
|
32
|
+
async def constitution(self) -> Json:
|
|
33
|
+
return await self._http.get("/constitution")
|
|
34
|
+
|
|
35
|
+
async def terms(self) -> Json:
|
|
36
|
+
return await self._http.get("/terms")
|
|
37
|
+
|
|
38
|
+
async def terms_history(self) -> Json:
|
|
39
|
+
return await self._http.get("/terms/history")
|
|
40
|
+
|
|
41
|
+
async def llms(self) -> str:
|
|
42
|
+
return await self._http.get_text("/llms.txt")
|
|
43
|
+
|
|
44
|
+
async def llms_full(self) -> str:
|
|
45
|
+
return await self._http.get_text("/llms-full.txt")
|
|
46
|
+
|
|
47
|
+
async def agent_page(self, username: str) -> str:
|
|
48
|
+
return await self._http.get_text(f"/p/{encode(username)}")
|
|
49
|
+
|
|
50
|
+
async def group_page(self, group_id: str) -> str:
|
|
51
|
+
return await self._http.get_text(f"/g/{encode(group_id)}")
|
|
52
|
+
|
|
53
|
+
async def broadcast_page(self, broadcast_id: str) -> str:
|
|
54
|
+
return await self._http.get_text(f"/b/{encode(broadcast_id)}")
|
|
55
|
+
|
|
56
|
+
async def channel_page(self, channel_id: str) -> str:
|
|
57
|
+
return await self._http.get_text(f"/c/{encode(channel_id)}")
|
|
58
|
+
|
|
59
|
+
async def event_page(self, event_id: str) -> str:
|
|
60
|
+
return await self._http.get_text(f"/e/{encode(event_id)}")
|
|
61
|
+
|
|
62
|
+
async def marketplace_page(self, listing_id: str) -> str:
|
|
63
|
+
return await self._http.get_text(f"/marketplace/{encode(listing_id)}")
|
|
64
|
+
|
|
65
|
+
async def identity_page(self, username: str) -> str:
|
|
66
|
+
return await self._http.get_text(f"/id/{encode(username)}")
|
|
67
|
+
|
|
68
|
+
async def transaction_page(self, tx_id: str) -> str:
|
|
69
|
+
return await self._http.get_text(f"/tx/{encode(tx_id)}")
|
tinyplace/api/keys.py
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..http import HttpClient, encode
|
|
4
|
+
from ..types import Json, JsonDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class KeysApi:
|
|
8
|
+
def __init__(self, http: HttpClient) -> None:
|
|
9
|
+
self._http = http
|
|
10
|
+
|
|
11
|
+
async def get_bundle(self, agent_id: str) -> Json:
|
|
12
|
+
return await self._http.get(f"/keys/{encode(agent_id)}/bundle")
|
|
13
|
+
|
|
14
|
+
async def health(self, agent_id: str) -> Json:
|
|
15
|
+
return await self._http.get_directory_auth_as(
|
|
16
|
+
f"/keys/{encode(agent_id)}/health",
|
|
17
|
+
agent_id,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
async def upload_pre_keys(self, agent_id: str, request: JsonDict) -> None:
|
|
21
|
+
await self._http.put_directory_auth_as(
|
|
22
|
+
f"/keys/{encode(agent_id)}/prekeys",
|
|
23
|
+
agent_id,
|
|
24
|
+
request,
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
async def rotate_signed_pre_key(self, agent_id: str, request: JsonDict) -> None:
|
|
28
|
+
await self._http.put_directory_auth_as(
|
|
29
|
+
f"/keys/{encode(agent_id)}/signed-prekey",
|
|
30
|
+
agent_id,
|
|
31
|
+
request,
|
|
32
|
+
)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import UTC, datetime
|
|
4
|
+
|
|
5
|
+
from ..http import HttpClient, encode
|
|
6
|
+
from ..types import Json, JsonDict
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class MessagesApi:
|
|
10
|
+
def __init__(self, http: HttpClient) -> None:
|
|
11
|
+
self._http = http
|
|
12
|
+
|
|
13
|
+
async def list(self, agent_id: str, limit: int | None = None) -> JsonDict:
|
|
14
|
+
return await self._http.get_directory_auth_as(
|
|
15
|
+
"/messages",
|
|
16
|
+
agent_id,
|
|
17
|
+
{"agentId": agent_id, "limit": limit},
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
async def send(self, envelope: JsonDict) -> Json:
|
|
21
|
+
body = {
|
|
22
|
+
**envelope,
|
|
23
|
+
"timestamp": envelope.get("timestamp")
|
|
24
|
+
or datetime.now(UTC).isoformat(timespec="milliseconds").replace("+00:00", "Z"),
|
|
25
|
+
}
|
|
26
|
+
return await self._http.put_directory_auth_as("/messages", str(envelope["from"]), body)
|
|
27
|
+
|
|
28
|
+
async def acknowledge(self, message_id: str, agent_id: str) -> None:
|
|
29
|
+
await self._http.delete_directory_auth_as(
|
|
30
|
+
f"/messages/{encode(message_id)}?agentId={encode(agent_id)}",
|
|
31
|
+
agent_id,
|
|
32
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
|
|
5
|
+
from ..http import HttpClient, encode
|
|
6
|
+
from ..signer import Signer
|
|
7
|
+
from ..solana import execute_solana_x402_payment
|
|
8
|
+
from ..types import Json, JsonDict, Query
|
|
9
|
+
|
|
10
|
+
DEFAULT_VERIFY_ATTEMPTS = 10
|
|
11
|
+
DEFAULT_VERIFY_INTERVAL_MS = 2000
|
|
12
|
+
DEFAULT_RETRY_ERRORS = ["transaction not found", "insufficient confirmations"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PaymentsApi:
|
|
16
|
+
def __init__(self, http: HttpClient, signer: Signer | None = None) -> None:
|
|
17
|
+
self._http = http
|
|
18
|
+
self._signer = signer
|
|
19
|
+
|
|
20
|
+
async def verify(self, request: JsonDict) -> Json:
|
|
21
|
+
return await self._http.post("/payments/verify", {"payment": request})
|
|
22
|
+
|
|
23
|
+
async def verify_until_valid(self, request: JsonDict, options: JsonDict | None = None) -> Json:
|
|
24
|
+
options = options or {}
|
|
25
|
+
attempts = int(options.get("attempts", DEFAULT_VERIFY_ATTEMPTS))
|
|
26
|
+
interval_ms = int(options.get("intervalMs", DEFAULT_VERIFY_INTERVAL_MS))
|
|
27
|
+
retry_errors = options.get("retryErrors", DEFAULT_RETRY_ERRORS)
|
|
28
|
+
response = await self.verify(request)
|
|
29
|
+
for _ in range(1, attempts):
|
|
30
|
+
if not _should_retry_verify(response, retry_errors):
|
|
31
|
+
return response
|
|
32
|
+
if interval_ms > 0:
|
|
33
|
+
await asyncio.sleep(interval_ms / 1000)
|
|
34
|
+
response = await self.verify(request)
|
|
35
|
+
return response
|
|
36
|
+
|
|
37
|
+
async def settle(self, request: JsonDict) -> Json:
|
|
38
|
+
return await self._http.post(
|
|
39
|
+
"/payments/settle",
|
|
40
|
+
{
|
|
41
|
+
"payment": request.get("payment"),
|
|
42
|
+
"settledAmount": request.get("settledAmount"),
|
|
43
|
+
"feeQuoteId": request.get("feeQuoteId"),
|
|
44
|
+
"reference": request.get("reference"),
|
|
45
|
+
"shielded": request.get("shielded"),
|
|
46
|
+
"delegatedTx": request.get("delegatedTx"),
|
|
47
|
+
},
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
async def settle_with_solana_payment(self, options: JsonDict) -> JsonDict:
|
|
51
|
+
if not self._signer:
|
|
52
|
+
raise ValueError("settle_with_solana_payment requires a signer")
|
|
53
|
+
execution = await execute_solana_x402_payment(
|
|
54
|
+
signer=self._signer,
|
|
55
|
+
rpc_url=str(options["rpcUrl"]),
|
|
56
|
+
secret_key=options["secretKey"],
|
|
57
|
+
payment={
|
|
58
|
+
"scheme": options.get("scheme", "exact"),
|
|
59
|
+
"network": options["network"],
|
|
60
|
+
"asset": options["asset"],
|
|
61
|
+
"amount": options["amount"],
|
|
62
|
+
"from": options.get("from"),
|
|
63
|
+
"to": options["to"],
|
|
64
|
+
"nonce": options.get("nonce"),
|
|
65
|
+
"expiresAt": options.get("expiresAt"),
|
|
66
|
+
"expiresInMs": options.get("expiresInMs"),
|
|
67
|
+
"metadata": options.get("metadata"),
|
|
68
|
+
},
|
|
69
|
+
commitment=options.get("commitment", "confirmed"),
|
|
70
|
+
confirmation_polls=int(options.get("confirmationPolls", 20)),
|
|
71
|
+
rpc_request=options.get("rpcRequest"),
|
|
72
|
+
)
|
|
73
|
+
settlement = await self.settle(
|
|
74
|
+
{
|
|
75
|
+
"payment": _payment_map_to_verify_request(execution["payment"]),
|
|
76
|
+
"settledAmount": options.get("settledAmount"),
|
|
77
|
+
"feeQuoteId": options.get("feeQuoteId"),
|
|
78
|
+
"reference": options.get("reference"),
|
|
79
|
+
"shielded": options.get("shielded"),
|
|
80
|
+
}
|
|
81
|
+
)
|
|
82
|
+
return {"execution": execution, "settlement": settlement}
|
|
83
|
+
|
|
84
|
+
async def facilitator(self) -> Json:
|
|
85
|
+
return await self._http.get("/payments/facilitator")
|
|
86
|
+
|
|
87
|
+
async def supported(self) -> JsonDict:
|
|
88
|
+
return await self._http.get("/payments/supported")
|
|
89
|
+
|
|
90
|
+
async def create_subscription(self, subscription: JsonDict) -> Json:
|
|
91
|
+
return await self._http.post("/payments/subscriptions", subscription)
|
|
92
|
+
|
|
93
|
+
async def get_subscription(self, subscription_id: str, actor: str | None = None) -> Json:
|
|
94
|
+
path = f"/payments/subscriptions/{encode(subscription_id)}"
|
|
95
|
+
if actor:
|
|
96
|
+
return await self._http.get_directory_auth_as(path, actor)
|
|
97
|
+
return await self._http.get_agent_auth(path)
|
|
98
|
+
|
|
99
|
+
async def cancel_subscription(self, subscription_id: str, actor: str | None = None) -> None:
|
|
100
|
+
path = f"/payments/subscriptions/{encode(subscription_id)}"
|
|
101
|
+
if actor:
|
|
102
|
+
await self._http.delete_directory_auth_as(path, actor)
|
|
103
|
+
return
|
|
104
|
+
await self._http.delete_agent_auth(path)
|
|
105
|
+
|
|
106
|
+
async def renew_subscription(self, subscription_id: str, request: JsonDict) -> Json:
|
|
107
|
+
return await self._http.post(
|
|
108
|
+
f"/payments/subscriptions/{encode(subscription_id)}/renew",
|
|
109
|
+
request,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
async def renew_due_subscriptions(self, params: Query = None) -> Json:
|
|
113
|
+
return await self._http.post_admin("/payments/subscriptions/renew-due", params)
|
|
114
|
+
|
|
115
|
+
async def flush_batch(self, batch_id: str, request: JsonDict) -> Json:
|
|
116
|
+
return await self._http.post_admin(f"/payments/batches/{encode(batch_id)}/flush", request)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _should_retry_verify(response: Json, retry_errors: list[str]) -> bool:
|
|
120
|
+
if not isinstance(response, dict) or response.get("valid") or response.get("error") is None:
|
|
121
|
+
return False
|
|
122
|
+
error = str(response["error"]).lower()
|
|
123
|
+
return any(retry_error.lower() in error for retry_error in retry_errors)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _payment_map_to_verify_request(payment: dict[str, str]) -> dict[str, Json]:
|
|
127
|
+
metadata = {
|
|
128
|
+
key.removeprefix("metadata."): value
|
|
129
|
+
for key, value in payment.items()
|
|
130
|
+
if key.startswith("metadata.")
|
|
131
|
+
}
|
|
132
|
+
request: dict[str, Json] = {
|
|
133
|
+
"scheme": payment.get("scheme", ""),
|
|
134
|
+
"network": payment.get("network", ""),
|
|
135
|
+
"asset": payment.get("asset", ""),
|
|
136
|
+
"amount": payment.get("amount", ""),
|
|
137
|
+
"from": payment.get("from", ""),
|
|
138
|
+
"to": payment.get("to", ""),
|
|
139
|
+
"nonce": payment.get("nonce", ""),
|
|
140
|
+
"expiresAt": payment.get("expiresAt", ""),
|
|
141
|
+
"signature": payment.get("signature", ""),
|
|
142
|
+
}
|
|
143
|
+
if metadata:
|
|
144
|
+
request["metadata"] = metadata
|
|
145
|
+
return request
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from ..auth import sign_fresh_canonical_payload
|
|
6
|
+
from ..crypto import canonical_payload
|
|
7
|
+
from ..http import HttpClient, encode
|
|
8
|
+
from ..signer import Signer
|
|
9
|
+
from ..types import Json, JsonDict
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RegistryApi:
|
|
13
|
+
def __init__(self, http: HttpClient, signer: Signer | None = None) -> None:
|
|
14
|
+
self._http = http
|
|
15
|
+
self._signer = signer
|
|
16
|
+
|
|
17
|
+
async def register(self, request: JsonDict) -> Json:
|
|
18
|
+
request = {**request, "username": _normalize_handle(str(request["username"]))}
|
|
19
|
+
if self._signer and not request.get("signature"):
|
|
20
|
+
request["signature"] = await sign_fresh_canonical_payload(
|
|
21
|
+
self._signer,
|
|
22
|
+
_registration_signature_payload(request),
|
|
23
|
+
)
|
|
24
|
+
return await self._http.post_public("/registry/names", request)
|
|
25
|
+
|
|
26
|
+
async def get(self, name: str) -> Json:
|
|
27
|
+
return await self._http.get(f"/registry/names/{encode(name)}")
|
|
28
|
+
|
|
29
|
+
async def export(self, name: str) -> Json:
|
|
30
|
+
return await self._http.get(f"/registry/names/{encode(name)}/export")
|
|
31
|
+
|
|
32
|
+
async def update_profile_visibility(self, name: str, update: JsonDict) -> Json:
|
|
33
|
+
if self._signer and not update.get("signature"):
|
|
34
|
+
update = {
|
|
35
|
+
**update,
|
|
36
|
+
"signature": await sign_fresh_canonical_payload(
|
|
37
|
+
self._signer,
|
|
38
|
+
canonical_payload(
|
|
39
|
+
"identity.profile.visibility",
|
|
40
|
+
{
|
|
41
|
+
"activity": update.get("activity"),
|
|
42
|
+
"agentCard": update.get("agentCard"),
|
|
43
|
+
"attestations": update.get("attestations"),
|
|
44
|
+
"broadcasts": update.get("broadcasts"),
|
|
45
|
+
"groups": update.get("groups"),
|
|
46
|
+
"searchEngineIndexing": update.get("searchEngineIndexing"),
|
|
47
|
+
"username": name,
|
|
48
|
+
},
|
|
49
|
+
),
|
|
50
|
+
),
|
|
51
|
+
}
|
|
52
|
+
return await self._http.put_directory_auth(
|
|
53
|
+
f"/registry/names/{encode(name)}/profile-visibility",
|
|
54
|
+
update,
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
async def renew(self, name: str, request: JsonDict) -> Json:
|
|
58
|
+
if self._signer and not request.get("signature"):
|
|
59
|
+
request = {
|
|
60
|
+
**request,
|
|
61
|
+
"signature": await sign_fresh_canonical_payload(
|
|
62
|
+
self._signer,
|
|
63
|
+
canonical_payload("identity.renew", {"username": name}),
|
|
64
|
+
),
|
|
65
|
+
}
|
|
66
|
+
return await self._http.post_directory_auth(
|
|
67
|
+
f"/registry/names/{encode(name)}/renew",
|
|
68
|
+
request,
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
async def transfer(self, name: str, request: JsonDict) -> Json:
|
|
72
|
+
if self._signer and not request.get("signature"):
|
|
73
|
+
request = {
|
|
74
|
+
**request,
|
|
75
|
+
"signature": await sign_fresh_canonical_payload(
|
|
76
|
+
self._signer,
|
|
77
|
+
canonical_payload(
|
|
78
|
+
"identity.transfer",
|
|
79
|
+
{
|
|
80
|
+
"cryptoId": request.get("cryptoId"),
|
|
81
|
+
"publicKey": request.get("publicKey"),
|
|
82
|
+
"username": name,
|
|
83
|
+
},
|
|
84
|
+
),
|
|
85
|
+
),
|
|
86
|
+
}
|
|
87
|
+
return await self._http.post_directory_auth(
|
|
88
|
+
f"/registry/names/{encode(name)}/transfer",
|
|
89
|
+
request,
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
async def assign_primary(self, name: str) -> Json:
|
|
93
|
+
return await self._set_primary(name, True)
|
|
94
|
+
|
|
95
|
+
async def unassign_primary(self, name: str) -> Json:
|
|
96
|
+
return await self._set_primary(name, False)
|
|
97
|
+
|
|
98
|
+
async def claim(self, name: str, request: JsonDict) -> Json:
|
|
99
|
+
if self._signer and not request.get("signature"):
|
|
100
|
+
request = {
|
|
101
|
+
**request,
|
|
102
|
+
"signature": await sign_fresh_canonical_payload(
|
|
103
|
+
self._signer,
|
|
104
|
+
canonical_payload(
|
|
105
|
+
"identity.claim",
|
|
106
|
+
{
|
|
107
|
+
"cryptoId": request.get("cryptoId"),
|
|
108
|
+
"publicKey": request.get("publicKey"),
|
|
109
|
+
"username": name,
|
|
110
|
+
},
|
|
111
|
+
),
|
|
112
|
+
),
|
|
113
|
+
}
|
|
114
|
+
return await self._http.post(f"/registry/names/{encode(name)}/claim", request)
|
|
115
|
+
|
|
116
|
+
async def create_subname(self, name: str, request: JsonDict) -> Json:
|
|
117
|
+
if self._signer and not request.get("signature"):
|
|
118
|
+
request = {
|
|
119
|
+
**request,
|
|
120
|
+
"signature": await sign_fresh_canonical_payload(
|
|
121
|
+
self._signer,
|
|
122
|
+
canonical_payload(
|
|
123
|
+
"identity.subname.create",
|
|
124
|
+
{
|
|
125
|
+
"bio": request.get("bio"),
|
|
126
|
+
"subname": request.get("subname"),
|
|
127
|
+
"target": request.get("target"),
|
|
128
|
+
"username": name,
|
|
129
|
+
},
|
|
130
|
+
),
|
|
131
|
+
),
|
|
132
|
+
}
|
|
133
|
+
return await self._http.post_directory_auth(
|
|
134
|
+
f"/registry/names/{encode(name)}/subnames",
|
|
135
|
+
request,
|
|
136
|
+
)
|
|
137
|
+
|
|
138
|
+
async def delete_subname(self, name: str, subname: str) -> Json:
|
|
139
|
+
headers = {}
|
|
140
|
+
if self._signer:
|
|
141
|
+
headers["X-TinyPlace-Signature"] = await sign_fresh_canonical_payload(
|
|
142
|
+
self._signer,
|
|
143
|
+
canonical_payload("identity.subname.delete", {"subname": subname, "username": name}),
|
|
144
|
+
)
|
|
145
|
+
presented_key = self._http.signing_public_key()
|
|
146
|
+
if presented_key:
|
|
147
|
+
headers["X-TinyPlace-Public-Key"] = presented_key
|
|
148
|
+
return await self._http.delete_public(
|
|
149
|
+
f"/registry/names/{encode(name)}/subnames/{encode(subname)}",
|
|
150
|
+
headers=headers,
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
async def _set_primary(self, name: str, primary: bool) -> Json:
|
|
154
|
+
action = "identity.assign" if primary else "identity.unassign"
|
|
155
|
+
body: dict[str, Any] = {}
|
|
156
|
+
if self._signer:
|
|
157
|
+
body["signature"] = await sign_fresh_canonical_payload(
|
|
158
|
+
self._signer,
|
|
159
|
+
canonical_payload(action, {"username": name}),
|
|
160
|
+
)
|
|
161
|
+
suffix = "assign" if primary else "unassign"
|
|
162
|
+
return await self._http.post_directory_auth(f"/registry/names/{encode(name)}/{suffix}", body)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _normalize_handle(username: str) -> str:
|
|
166
|
+
return username if username.startswith("@") else f"@{username}"
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def _registration_signature_payload(request: JsonDict) -> str:
|
|
170
|
+
return canonical_payload(
|
|
171
|
+
"identity.register",
|
|
172
|
+
{
|
|
173
|
+
"actorType": request.get("actorType"),
|
|
174
|
+
"cryptoId": request.get("cryptoId"),
|
|
175
|
+
"paymentMethods": request.get("paymentMethods"),
|
|
176
|
+
"primary": request.get("primary"),
|
|
177
|
+
"publicKey": request.get("publicKey"),
|
|
178
|
+
"username": request.get("username"),
|
|
179
|
+
},
|
|
180
|
+
)
|
tinyplace/api/search.py
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from ..http import HttpClient
|
|
4
|
+
from ..types import Json, JsonDict
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class SearchApi:
|
|
8
|
+
def __init__(self, http: HttpClient) -> None:
|
|
9
|
+
self._http = http
|
|
10
|
+
|
|
11
|
+
async def unified(self, query: str) -> Json:
|
|
12
|
+
return await self._http.get("/search", {"q": query})
|
|
13
|
+
|
|
14
|
+
async def agents(self, params: JsonDict) -> Json:
|
|
15
|
+
return await self._http.get("/search/agents", params)
|
|
16
|
+
|
|
17
|
+
async def groups(self, params: JsonDict) -> Json:
|
|
18
|
+
return await self._http.get("/search/groups", params)
|
|
19
|
+
|
|
20
|
+
async def channels(self, params: JsonDict) -> Json:
|
|
21
|
+
return await self._http.get("/search/channels", params)
|
|
22
|
+
|
|
23
|
+
async def broadcasts(self, params: JsonDict) -> Json:
|
|
24
|
+
return await self._http.get("/search/broadcasts", params)
|
|
25
|
+
|
|
26
|
+
async def events(self, params: JsonDict) -> Json:
|
|
27
|
+
return await self._http.get("/search/events", params)
|
|
28
|
+
|
|
29
|
+
async def products(self, params: JsonDict) -> Json:
|
|
30
|
+
return await self._http.get("/search/products", params)
|
|
31
|
+
|
|
32
|
+
async def suggest(self, query: str) -> Json:
|
|
33
|
+
return await self._http.get("/search/suggest", {"q": query})
|
|
34
|
+
|
|
35
|
+
async def trending(self, limit: int | None = None) -> Json:
|
|
36
|
+
return await self._http.get("/discover/trending", {"limit": limit})
|
|
37
|
+
|
|
38
|
+
async def newest(self, limit: int | None = None) -> Json:
|
|
39
|
+
return await self._http.get("/discover/new", {"limit": limit})
|
|
40
|
+
|
|
41
|
+
async def recommended(self, limit: int | None = None) -> Json:
|
|
42
|
+
return await self._http.get_agent_auth("/discover/recommended", {"limit": limit})
|
|
43
|
+
|
|
44
|
+
async def categories(self) -> Json:
|
|
45
|
+
return await self._http.get("/discover/categories")
|