fleetmesh-node 0.2.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.
Files changed (338) hide show
  1. fleetmesh_node-0.2.0.dist-info/METADATA +57 -0
  2. fleetmesh_node-0.2.0.dist-info/RECORD +338 -0
  3. fleetmesh_node-0.2.0.dist-info/WHEEL +5 -0
  4. fleetmesh_node-0.2.0.dist-info/entry_points.txt +2 -0
  5. fleetmesh_node-0.2.0.dist-info/licenses/LICENSE +121 -0
  6. fleetmesh_node-0.2.0.dist-info/top_level.txt +2 -0
  7. node/RELEASE +1 -0
  8. node/__init__.py +35 -0
  9. node/app/__init__.py +49 -0
  10. node/app/commands.py +265 -0
  11. node/app/config.py +131 -0
  12. node/app/control.py +670 -0
  13. node/app/core.py +867 -0
  14. node/app/plugins.py +76 -0
  15. node/app/router.py +83 -0
  16. node/app/socket.py +431 -0
  17. node/app/systemd.py +36 -0
  18. node/app/views.py +204 -0
  19. node/channels/__init__.py +378 -0
  20. node/channels/bus.py +331 -0
  21. node/channels/journal.py +46 -0
  22. node/commands/__init__.py +140 -0
  23. node/commands/adapter.py +210 -0
  24. node/commands/base.py +278 -0
  25. node/commands/cluster_cmds.py +183 -0
  26. node/commands/compute_cmds.py +401 -0
  27. node/commands/core.py +474 -0
  28. node/commands/gossip_cmds.py +106 -0
  29. node/commands/nsite_cmds.py +141 -0
  30. node/commands/peering.py +402 -0
  31. node/commands/plugin_cmds.py +135 -0
  32. node/commands/sidecar_cmds.py +120 -0
  33. node/commands/sync.py +388 -0
  34. node/commands/transport_cmds.py +451 -0
  35. node/commands/video_cmds.py +99 -0
  36. node/compartment/__init__.py +124 -0
  37. node/compartment/base.py +868 -0
  38. node/compartment/command.py +458 -0
  39. node/compartment/context.py +163 -0
  40. node/compartment/every.py +108 -0
  41. node/compartment/handler.py +131 -0
  42. node/compartment/lifecycle.py +460 -0
  43. node/compartment/lint.py +388 -0
  44. node/compartment/need.py +158 -0
  45. node/compartment/problem.py +521 -0
  46. node/compartment/refusals.py +120 -0
  47. node/compartment/registry.py +790 -0
  48. node/compartment/schema.py +290 -0
  49. node/compartment/sidecar.py +499 -0
  50. node/compartment/stores.py +662 -0
  51. node/compartment/wire.py +225 -0
  52. node/compartment/worker.py +61 -0
  53. node/config/__init__.py +43 -0
  54. node/config/base.py +116 -0
  55. node/config/change.py +441 -0
  56. node/config/changestore.py +201 -0
  57. node/config/compartments.py +1144 -0
  58. node/config/node.py +318 -0
  59. node/config/parameter.py +563 -0
  60. node/config/protocols.py +274 -0
  61. node/config/records.py +171 -0
  62. node/examples/__init__.py +1 -0
  63. node/examples/demo.py +226 -0
  64. node/examples/follower.py +46 -0
  65. node/interfaces.py +235 -0
  66. node/keystore/__init__.py +27 -0
  67. node/keystore/core.py +103 -0
  68. node/keystore/enclave.py +688 -0
  69. node/meshnode.py +111 -0
  70. node/metrics/__init__.py +396 -0
  71. node/metrics/adapters.py +102 -0
  72. node/metrics/face.py +110 -0
  73. node/planes/__init__.py +21 -0
  74. node/planes/identity/__init__.py +20 -0
  75. node/planes/identity/announce/__init__.py +9 -0
  76. node/planes/identity/announce/compartment.py +447 -0
  77. node/planes/identity/announce/types.py +26 -0
  78. node/planes/identity/discovery/__init__.py +9 -0
  79. node/planes/identity/discovery/compartment.py +522 -0
  80. node/planes/identity/discovery/types.py +13 -0
  81. node/planes/identity/linked/__init__.py +1 -0
  82. node/planes/identity/linked/compartment.py +270 -0
  83. node/planes/identity/linked/types.py +25 -0
  84. node/planes/identity/presence/__init__.py +7 -0
  85. node/planes/identity/presence/compartment.py +409 -0
  86. node/planes/identity/presence/types.py +14 -0
  87. node/planes/operations/__init__.py +13 -0
  88. node/planes/operations/scheduler/__init__.py +9 -0
  89. node/planes/operations/scheduler/compartment.py +353 -0
  90. node/planes/operations/scheduler/types.py +21 -0
  91. node/planes/operations/updates/__init__.py +17 -0
  92. node/planes/operations/updates/core.py +310 -0
  93. node/planes/operations/updates/store.py +79 -0
  94. node/planes/operations/updates/types.py +20 -0
  95. node/planes/peering/__init__.py +28 -0
  96. node/planes/peering/budget/__init__.py +43 -0
  97. node/planes/peering/budget/chain.py +43 -0
  98. node/planes/peering/budget/core.py +537 -0
  99. node/planes/peering/budget/evaluation.py +80 -0
  100. node/planes/peering/budget/meter.py +100 -0
  101. node/planes/peering/budget/receipts.py +439 -0
  102. node/planes/peering/budget/shape.py +101 -0
  103. node/planes/peering/budget/tick.py +110 -0
  104. node/planes/peering/budget/types.py +67 -0
  105. node/planes/peering/budget/windows.py +346 -0
  106. node/planes/peering/capacity/__init__.py +7 -0
  107. node/planes/peering/capacity/compartment.py +342 -0
  108. node/planes/peering/capacity/types.py +24 -0
  109. node/planes/peering/peering/__init__.py +25 -0
  110. node/planes/peering/peering/core.py +464 -0
  111. node/planes/peering/peering/envelope.py +142 -0
  112. node/planes/peering/peering/handshake.py +198 -0
  113. node/planes/peering/peering/shape.py +80 -0
  114. node/planes/peering/peering/types.py +39 -0
  115. node/planes/peering/reporter/__init__.py +42 -0
  116. node/planes/peering/reporter/constants.py +29 -0
  117. node/planes/peering/reporter/core.py +299 -0
  118. node/planes/peering/reporter/finding.py +74 -0
  119. node/planes/peering/reporter/findings.py +171 -0
  120. node/planes/peering/reporter/publish.py +285 -0
  121. node/planes/peering/reporter/reading.py +193 -0
  122. node/planes/peering/reporter/shape.py +86 -0
  123. node/planes/peering/reporter/types.py +50 -0
  124. node/planes/peering/strain/__init__.py +9 -0
  125. node/planes/peering/strain/compartment.py +204 -0
  126. node/planes/peering/strain/types.py +21 -0
  127. node/planes/peering/trust/__init__.py +9 -0
  128. node/planes/peering/trust/compartment.py +239 -0
  129. node/planes/peering/trust/types.py +30 -0
  130. node/planes/services/__init__.py +33 -0
  131. node/planes/services/compute/__init__.py +160 -0
  132. node/planes/services/compute/common.py +134 -0
  133. node/planes/services/compute/compartment.py +681 -0
  134. node/planes/services/compute/executors/__init__.py +121 -0
  135. node/planes/services/compute/executors/agent.py +840 -0
  136. node/planes/services/compute/executors/bacalhau.py +605 -0
  137. node/planes/services/compute/executors/base.py +127 -0
  138. node/planes/services/compute/executors/harness.py +439 -0
  139. node/planes/services/compute/executors/mcp.py +454 -0
  140. node/planes/services/compute/executors/media.py +898 -0
  141. node/planes/services/compute/executors/nondet.py +283 -0
  142. node/planes/services/compute/executors/providers.py +499 -0
  143. node/planes/services/compute/executors/registry.py +140 -0
  144. node/planes/services/compute/executors/wasm.py +244 -0
  145. node/planes/services/compute/executors/zk.py +604 -0
  146. node/planes/services/compute/jobs.py +227 -0
  147. node/planes/services/compute/load.py +180 -0
  148. node/planes/services/compute/market.py +433 -0
  149. node/planes/services/compute/sandbox.py +282 -0
  150. node/planes/services/compute/types.py +84 -0
  151. node/planes/services/compute_wire/__init__.py +58 -0
  152. node/planes/services/compute_wire/compartment.py +256 -0
  153. node/planes/services/compute_wire/messages.py +320 -0
  154. node/planes/services/compute_wire/sealing.py +147 -0
  155. node/planes/services/compute_wire/session.py +572 -0
  156. node/planes/services/compute_wire/types.py +49 -0
  157. node/planes/services/compute_wire/verify.py +331 -0
  158. node/planes/services/dvm/__init__.py +24 -0
  159. node/planes/services/dvm/gateway.py +526 -0
  160. node/planes/services/dvm/protocol.py +35 -0
  161. node/planes/services/dvm/translate.py +15 -0
  162. node/planes/services/dvm/types.py +25 -0
  163. node/planes/services/gateway/__init__.py +40 -0
  164. node/planes/services/gateway/settle.py +279 -0
  165. node/planes/services/gateway/translate.py +644 -0
  166. node/planes/services/gossip/__init__.py +22 -0
  167. node/planes/services/gossip/core.py +929 -0
  168. node/planes/services/gossip/envelope.py +154 -0
  169. node/planes/services/gossip/mesh.py +127 -0
  170. node/planes/services/gossip/types.py +50 -0
  171. node/planes/services/nsite/__init__.py +7 -0
  172. node/planes/services/nsite/client.py +62 -0
  173. node/planes/services/nsite/compartment.py +752 -0
  174. node/planes/services/nsite/types.py +44 -0
  175. node/planes/services/paidfetch/__init__.py +7 -0
  176. node/planes/services/paidfetch/compartment.py +862 -0
  177. node/planes/services/paidfetch/types.py +99 -0
  178. node/planes/services/settle/__init__.py +27 -0
  179. node/planes/services/settle/compartment.py +384 -0
  180. node/planes/services/settle/engine.py +456 -0
  181. node/planes/services/settle/gateways.py +541 -0
  182. node/planes/services/settle/payment.py +512 -0
  183. node/planes/services/settle/store.py +74 -0
  184. node/planes/services/settle/types.py +58 -0
  185. node/planes/services/sidecars/__init__.py +8 -0
  186. node/planes/services/sidecars/compartment.py +461 -0
  187. node/planes/services/sidecars/manifest.py +304 -0
  188. node/planes/services/sidecars/manifests/10-grasp.json +37 -0
  189. node/planes/services/sidecars/manifests/20-ncontainer.json +31 -0
  190. node/planes/services/sidecars/manifests/30-magpie.json +28 -0
  191. node/planes/services/sidecars/types.py +43 -0
  192. node/planes/services/video/__init__.py +106 -0
  193. node/planes/services/video/barter.py +407 -0
  194. node/planes/services/video/cli.py +158 -0
  195. node/planes/services/video/compartment.py +932 -0
  196. node/planes/services/video/ingest.py +524 -0
  197. node/planes/services/video/ledger.py +705 -0
  198. node/planes/services/video/packager.py +847 -0
  199. node/planes/services/video/player.py +456 -0
  200. node/planes/services/video/timed_segments.py +124 -0
  201. node/planes/services/video/transit.py +478 -0
  202. node/planes/store/__init__.py +24 -0
  203. node/planes/store/blob_replication/__init__.py +7 -0
  204. node/planes/store/blob_replication/compartment.py +496 -0
  205. node/planes/store/blob_replication/types.py +35 -0
  206. node/planes/store/blob_wire/__init__.py +48 -0
  207. node/planes/store/blob_wire/client.py +57 -0
  208. node/planes/store/blob_wire/core.py +359 -0
  209. node/planes/store/blob_wire/intake.py +74 -0
  210. node/planes/store/blob_wire/messages.py +85 -0
  211. node/planes/store/projected/__init__.py +7 -0
  212. node/planes/store/projected/compartment.py +258 -0
  213. node/planes/store/projected/types.py +29 -0
  214. node/planes/store/retention/__init__.py +7 -0
  215. node/planes/store/retention/compartment.py +214 -0
  216. node/planes/store/retention/types.py +12 -0
  217. node/planes/store/sync/__init__.py +35 -0
  218. node/planes/store/sync/core.py +311 -0
  219. node/planes/store/sync/envelope.py +106 -0
  220. node/planes/store/sync/handover.py +206 -0
  221. node/planes/store/sync/initiator.py +128 -0
  222. node/planes/store/sync/responder.py +114 -0
  223. node/planes/store/sync/rules.py +179 -0
  224. node/planes/store/sync/shape.py +80 -0
  225. node/planes/store/sync/types.py +51 -0
  226. node/planes/transport/__init__.py +23 -0
  227. node/planes/transport/bridge/__init__.py +13 -0
  228. node/planes/transport/bridge/compartment.py +785 -0
  229. node/planes/transport/bridge/ratelimit.py +44 -0
  230. node/planes/transport/bridge/types.py +43 -0
  231. node/planes/transport/lan/__init__.py +9 -0
  232. node/planes/transport/lan/compartment.py +446 -0
  233. node/planes/transport/lan/types.py +25 -0
  234. node/planes/transport/link/__init__.py +7 -0
  235. node/planes/transport/link/compartment.py +257 -0
  236. node/planes/transport/link/types.py +27 -0
  237. node/planes/transport/mediation/__init__.py +45 -0
  238. node/planes/transport/mediation/compartment.py +238 -0
  239. node/planes/transport/mediation/leaf.py +107 -0
  240. node/planes/transport/mediation/messages.py +302 -0
  241. node/planes/transport/mediation/service.py +233 -0
  242. node/planes/transport/mediation/store.py +37 -0
  243. node/planes/transport/mediation/types.py +20 -0
  244. node/planes/transport/p2p/__init__.py +7 -0
  245. node/planes/transport/p2p/compartment.py +349 -0
  246. node/planes/transport/p2p/types.py +22 -0
  247. node/protocols/__init__.py +277 -0
  248. node/protocols/activitypub/__init__.py +56 -0
  249. node/protocols/activitypub/actor.py +230 -0
  250. node/protocols/activitypub/federation.py +490 -0
  251. node/protocols/activitypub/media.py +145 -0
  252. node/protocols/activitypub/models.py +286 -0
  253. node/protocols/activitypub/package.py +271 -0
  254. node/protocols/activitypub/store.py +564 -0
  255. node/protocols/arcade/__init__.py +26 -0
  256. node/protocols/atproto/__init__.py +13 -0
  257. node/protocols/atproto/firehose.py +126 -0
  258. node/protocols/atproto/metering.py +165 -0
  259. node/protocols/atproto/package.py +155 -0
  260. node/protocols/atproto/repo.py +250 -0
  261. node/protocols/atproto/xrpc.py +285 -0
  262. node/protocols/base.py +109 -0
  263. node/protocols/circles/__init__.py +29 -0
  264. node/protocols/dm/__init__.py +50 -0
  265. node/protocols/grasp/__init__.py +22 -0
  266. node/protocols/isotime.py +15 -0
  267. node/protocols/nostr/__init__.py +51 -0
  268. node/protocols/nostr/blobstore.py +383 -0
  269. node/protocols/nostr/blossom.py +627 -0
  270. node/protocols/nostr/client.py +377 -0
  271. node/protocols/nostr/descriptor.py +211 -0
  272. node/protocols/nostr/eventstore.py +304 -0
  273. node/protocols/nostr/package.py +182 -0
  274. node/protocols/nostr/peers.py +109 -0
  275. node/protocols/nostr/relayserver.py +656 -0
  276. node/protocols/p2panda/__init__.py +22 -0
  277. node/protocols/p2panda/identity.py +19 -0
  278. node/protocols/p2panda/package.py +236 -0
  279. node/protocols/p2panda/rung.py +152 -0
  280. node/protocols/p2panda/store.py +273 -0
  281. node/protocols/rhio/__init__.py +33 -0
  282. node/protocols/rhio/package.py +275 -0
  283. node/protocols/rhio/rung.py +136 -0
  284. node/protocols/rhio/store.py +1423 -0
  285. node/protocols/ssb/muxrpc.py +256 -0
  286. node/protocols/ssb/package.py +267 -0
  287. node/protocols/textile/__init__.py +111 -0
  288. node/protocols/textile/transducers.py +259 -0
  289. node/release.py +119 -0
  290. node/storage/__init__.py +88 -0
  291. node/storage/accounts.py +305 -0
  292. node/storage/adapter.py +161 -0
  293. node/storage/base.py +385 -0
  294. node/storage/dialect.py +209 -0
  295. node/storage/grants.py +317 -0
  296. node/storage/locator.py +150 -0
  297. node/storage/postgres.py +294 -0
  298. node/storage/projected.py +953 -0
  299. node/storage/sharding.py +448 -0
  300. node/storage/standing.py +568 -0
  301. node/testnet/__init__.py +9 -0
  302. node/testnet/cluster.py +731 -0
  303. node/testnet/fips_daemon.py +230 -0
  304. node/transport/__init__.py +94 -0
  305. node/transport/const.py +110 -0
  306. node/transport/didcomm_inbound.py +251 -0
  307. node/transport/dnstxt.py +169 -0
  308. node/transport/fips_rung/__init__.py +67 -0
  309. node/transport/fips_rung/core.py +666 -0
  310. node/transport/fips_rung/listener.py +96 -0
  311. node/transport/fips_rung/types.py +12 -0
  312. node/transport/host.py +334 -0
  313. node/transport/iroh_rung/__init__.py +29 -0
  314. node/transport/iroh_rung/core.py +477 -0
  315. node/transport/iroh_rung/quic.py +942 -0
  316. node/transport/iroh_rung/types.py +22 -0
  317. node/transport/ladder.py +145 -0
  318. node/transport/lineproto.py +49 -0
  319. node/transport/mdns.py +303 -0
  320. node/transport/netfetch.py +283 -0
  321. node/transport/relay_rung/__init__.py +23 -0
  322. node/transport/relay_rung/cooldown.py +87 -0
  323. node/transport/relay_rung/core.py +138 -0
  324. node/transport/relay_rung/inbound.py +161 -0
  325. node/transport/relay_rung/ledger.py +104 -0
  326. node/transport/relay_rung/listen.py +121 -0
  327. node/transport/relay_rung/outbound.py +181 -0
  328. node/transport/relay_rung/shape.py +71 -0
  329. node/transport/relay_rung/types.py +58 -0
  330. node/transport/relay_rung/wire.py +63 -0
  331. node/transport/router.py +761 -0
  332. node/transport/rung.py +276 -0
  333. node/transport/ssb_rung.py +166 -0
  334. node/transport/ws.py +502 -0
  335. sidecar/fleetmesh_announce.py +338 -0
  336. sidecar/fleetmesh_p2p.py +373 -0
  337. sidecar/fleetmesh_rhio.py +352 -0
  338. sidecar/fleetmesh_ssb.py +603 -0
@@ -0,0 +1,56 @@
1
+ """ActivityPub protocol package for Fleetmesh.
2
+
3
+ Supports:
4
+ - WebFinger (RFC 7033) and Actor JSON-LD documents.
5
+ - HTTP Message Signatures (RFC 9421 / draft-cavage).
6
+ - ActivityStreams 2.0 inbox and outbox federation.
7
+ - Persistent SQLite storage for actors, activities, and social graph.
8
+ - Blossom media attachment proxy.
9
+ - Unified ProtocolPackage ingress integration.
10
+ """
11
+ from __future__ import annotations
12
+
13
+ from .actor import Actor, WebFingerService, ed25519_pubkey_to_pem, parse_acct_uri
14
+ from .federation import FederationService, StrainScoreKeeper
15
+ from .media import ActivityPubMediaManager
16
+ from .models import (
17
+ AS_CONTEXT,
18
+ SECURITY_CONTEXT,
19
+ Activity,
20
+ Note,
21
+ build_accept_activity,
22
+ build_announce_activity,
23
+ build_create_activity,
24
+ build_delete_activity,
25
+ build_follow_activity,
26
+ build_note,
27
+ build_undo_activity,
28
+ parse_activity,
29
+ )
30
+ from .package import ActivityPubIdentityAdapter, ActivityPubProtocolPackage
31
+ from .store import ActivityPubStore
32
+
33
+ __all__ = [
34
+ 'AS_CONTEXT',
35
+ 'SECURITY_CONTEXT',
36
+ 'Actor',
37
+ 'WebFingerService',
38
+ 'ed25519_pubkey_to_pem',
39
+ 'parse_acct_uri',
40
+ 'Note',
41
+ 'Activity',
42
+ 'parse_activity',
43
+ 'build_note',
44
+ 'build_create_activity',
45
+ 'build_follow_activity',
46
+ 'build_accept_activity',
47
+ 'build_undo_activity',
48
+ 'build_delete_activity',
49
+ 'build_announce_activity',
50
+ 'ActivityPubStore',
51
+ 'FederationService',
52
+ 'StrainScoreKeeper',
53
+ 'ActivityPubMediaManager',
54
+ 'ActivityPubIdentityAdapter',
55
+ 'ActivityPubProtocolPackage',
56
+ ]
@@ -0,0 +1,230 @@
1
+ """ActivityPub Actor and WebFinger (RFC 7033) service.
2
+
3
+ Standard library only. Manages:
4
+ - WebFinger discovery (/.well-known/webfinger?resource=acct:user@domain).
5
+ - Actor JSON-LD document generation with W3C Security Vocabulary.
6
+ - Ed25519 and RSA SubjectPublicKeyInfo PEM encoding.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import base64
11
+ import json
12
+ from typing import Any, Mapping, Optional
13
+
14
+ from fleetmesh_core.conformance.errors import FleetmeshError
15
+
16
+ __all__ = [
17
+ 'ActorError',
18
+ 'ed25519_pubkey_to_pem',
19
+ 'parse_acct_uri',
20
+ 'Actor',
21
+ 'WebFingerService',
22
+ ]
23
+
24
+ # Standard Ed25519 SubjectPublicKeyInfo prefix (RFC 8410)
25
+ _ED25519_SPKI_PREFIX = bytes([
26
+ 0x30, 0x2a, # SEQUENCE (42 bytes)
27
+ 0x30, 0x05, # SEQUENCE (5 bytes)
28
+ 0x06, 0x03, 0x2b, 0x65, 0x70, # OID 1.3.101.112 (id-Ed25519)
29
+ 0x03, 0x21, 0x00, # BIT STRING (33 bytes, 0 unused bits)
30
+ ])
31
+
32
+
33
+ class ActorError(FleetmeshError):
34
+ """Base error for ActivityPub actor operations."""
35
+
36
+
37
+ def ed25519_pubkey_to_pem(raw_bytes: bytes) -> str:
38
+ """Format 32-byte Ed25519 raw public key as standard SubjectPublicKeyInfo PEM."""
39
+ if len(raw_bytes) != 32:
40
+ raise ActorError(f"invalid Ed25519 public key length: {len(raw_bytes)}")
41
+ der = _ED25519_SPKI_PREFIX + raw_bytes
42
+ b64 = base64.b64encode(der).decode('ascii')
43
+ # 64-character line wrapped
44
+ lines = [b64[i:i + 64] for i in range(0, len(b64), 64)]
45
+ body = '\n'.join(lines)
46
+ return f"-----BEGIN PUBLIC KEY-----\n{body}\n-----END PUBLIC KEY-----\n"
47
+
48
+
49
+ def parse_acct_uri(uri: str) -> tuple[str, str]:
50
+ """Parse an acct: URI into (username, domain).
51
+
52
+ Example: 'acct:node@example.com' -> ('node', 'example.com')
53
+ """
54
+ s = uri.strip()
55
+ if s.startswith('acct:'):
56
+ s = s[5:]
57
+ parts = s.split('@')
58
+ if len(parts) != 2 or not parts[0] or not parts[1]:
59
+ raise ActorError(f"invalid acct URI: {uri}")
60
+ return parts[0].lower(), parts[1].lower()
61
+
62
+
63
+ class Actor:
64
+ """An ActivityPub Actor profile."""
65
+
66
+ def __init__(
67
+ self,
68
+ domain: str,
69
+ username: str = "node",
70
+ name: str = "Fleetmesh Edge Node",
71
+ summary: str = "Autonomous Fleetmesh edge node",
72
+ public_key_pem: str = "",
73
+ actor_type: str = "Service",
74
+ icon_url: Optional[str] = None,
75
+ use_https: bool = True,
76
+ ):
77
+ self.domain = domain.lower().strip()
78
+ self.username = username.lower().strip()
79
+ self.name = name
80
+ self.summary = summary
81
+ self.public_key_pem = public_key_pem
82
+ self.actor_type = actor_type
83
+ self.icon_url = icon_url
84
+ self.scheme = "https" if use_https else "http"
85
+
86
+ @property
87
+ def acct_uri(self) -> str:
88
+ return f"acct:{self.username}@{self.domain}"
89
+
90
+ @property
91
+ def id(self) -> str:
92
+ return self.actor_url
93
+
94
+ @property
95
+ def actor_url(self) -> str:
96
+ return f"{self.scheme}://{self.domain}/users/{self.username}"
97
+
98
+ @property
99
+ def inbox_url(self) -> str:
100
+ return f"{self.actor_url}/inbox"
101
+
102
+ @property
103
+ def outbox_url(self) -> str:
104
+ return f"{self.actor_url}/outbox"
105
+
106
+ @property
107
+ def followers_url(self) -> str:
108
+ return f"{self.actor_url}/followers"
109
+
110
+ @property
111
+ def following_url(self) -> str:
112
+ return f"{self.actor_url}/following"
113
+
114
+ @property
115
+ def shared_inbox_url(self) -> str:
116
+ return f"{self.scheme}://{self.domain}/inbox"
117
+
118
+ @property
119
+ def public_key_id(self) -> str:
120
+ return f"{self.actor_url}#main-key"
121
+
122
+ def to_jsonld(self) -> Mapping[str, Any]:
123
+ """Produce W3C ActivityStreams 2.0 / ActivityPub Actor JSON-LD document."""
124
+ doc = {
125
+ '@context': [
126
+ 'https://www.w3.org/ns/activitystreams',
127
+ 'https://w3id.org/security/v1',
128
+ ],
129
+ 'id': self.actor_url,
130
+ 'type': self.actor_type,
131
+ 'following': self.following_url,
132
+ 'followers': self.followers_url,
133
+ 'inbox': self.inbox_url,
134
+ 'outbox': self.outbox_url,
135
+ 'preferredUsername': self.username,
136
+ 'name': self.name,
137
+ 'summary': self.summary,
138
+ 'url': self.actor_url,
139
+ 'endpoints': {
140
+ 'sharedInbox': self.shared_inbox_url,
141
+ },
142
+ 'publicKey': {
143
+ 'id': self.public_key_id,
144
+ 'owner': self.actor_url,
145
+ 'publicKeyPem': self.public_key_pem,
146
+ },
147
+ }
148
+ if self.icon_url:
149
+ doc['icon'] = {
150
+ 'type': 'Image',
151
+ 'mediaType': 'image/png',
152
+ 'url': self.icon_url,
153
+ }
154
+ return doc
155
+
156
+
157
+ class WebFingerService:
158
+ """Answers RFC 7033 WebFinger and Actor JSON-LD requests."""
159
+
160
+ def __init__(self, actor: Any):
161
+ if isinstance(actor, Mapping):
162
+ self.actors = dict(actor)
163
+ self.actor = next(iter(actor.values())) if actor else None
164
+ else:
165
+ self.actor = actor
166
+ self.actors = {actor.username: actor} if actor else {}
167
+
168
+ def handle_webfinger_request(
169
+ self,
170
+ params: dict[str, list[str]],
171
+ ) -> tuple[int, dict[str, str], bytes]:
172
+ """Dispatch GET /.well-known/webfinger."""
173
+ resource_list = params.get('resource', [])
174
+ if not resource_list:
175
+ body = json.dumps({'error': 'invalid_request', 'message': 'missing resource parameter'}).encode()
176
+ return 400, {'Content-Type': 'application/json'}, body
177
+
178
+ resource = resource_list[0].strip()
179
+
180
+ if self.actor is None:
181
+ body = json.dumps({'error': 'not_found', 'message': f"resource {resource} not found"}).encode()
182
+ return 404, {'Content-Type': 'application/json'}, body
183
+
184
+ actor = self.actor
185
+ # Check if requested resource matches this actor
186
+ matches = False
187
+ if resource == actor.acct_uri:
188
+ matches = True
189
+ elif resource == actor.actor_url:
190
+ matches = True
191
+ elif resource.startswith('acct:'):
192
+ try:
193
+ u, d = parse_acct_uri(resource)
194
+ if u == actor.username and d == actor.domain:
195
+ matches = True
196
+ except Exception:
197
+ pass
198
+
199
+ if not matches:
200
+ body = json.dumps({'error': 'not_found', 'message': f"resource {resource} not found"}).encode()
201
+ return 404, {'Content-Type': 'application/json'}, body
202
+
203
+ jrd = {
204
+ 'subject': actor.acct_uri,
205
+ 'aliases': [
206
+ actor.actor_url,
207
+ ],
208
+ 'links': [
209
+ {
210
+ 'rel': 'self',
211
+ 'type': 'application/activity+json',
212
+ 'href': actor.actor_url,
213
+ },
214
+ {
215
+ 'rel': 'http://webfinger.example.org/rel/profile-page',
216
+ 'type': 'text/html',
217
+ 'href': actor.actor_url,
218
+ },
219
+ ],
220
+ }
221
+ return 200, {'Content-Type': 'application/jrd+json; charset=utf-8'}, json.dumps(jrd).encode()
222
+
223
+ def handle_actor_request(self) -> tuple[int, dict[str, str], bytes]:
224
+ """Dispatch GET /users/<username>."""
225
+ if self.actor is None:
226
+ return 404, {'Content-Type': 'application/json'}, b'{"error": "not_found"}'
227
+ doc = self.actor.to_jsonld()
228
+ return 200, {'Content-Type': 'application/activity+json; charset=utf-8'}, json.dumps(doc).encode()
229
+
230
+ handle_webfinger = handle_webfinger_request