From 6da4dad273b8c87b51146c654389ba13fc3e8564 Mon Sep 17 00:00:00 2001
From: supersonicwisd1 <supersonicwisd1>
Date: Mon, 25 Nov 2024 10:13:41 +0100
Subject: [PATCH] working on the delivey and discory to enable sending of
 activities"

Signed-off-by: supersonicwisd1 <supersonicwisd1>
---
 examples/note_example.py          |  10 +++-
 examples/send_to_mastodon.py      |  94 ++++++++++++++++++++++++++++++
 requirements.txt                  |   3 +-
 src/pyfed/.DS_Store               | Bin 6148 -> 8196 bytes
 src/pyfed/federation/delivery.py  |   4 +-
 src/pyfed/federation/discovery.py |   8 ++-
 6 files changed, 113 insertions(+), 6 deletions(-)
 create mode 100644 examples/send_to_mastodon.py

diff --git a/examples/note_example.py b/examples/note_example.py
index 714d870..9cdc809 100644
--- a/examples/note_example.py
+++ b/examples/note_example.py
@@ -65,6 +65,7 @@ async def create_note_example():
         logger.info(f"Active key: {active_key}")
 
         # Initialize HTTP signature verifier with the active key paths
+        logger.info(f"Using key paths: {key_manager.keys_path}/{active_key.key_id}_private.pem and {key_manager.keys_path}/{active_key.key_id}_public.pem")
         signature_verifier = HTTPSignatureVerifier(
             private_key_path=f"{key_manager.keys_path}/{active_key.key_id}_private.pem",
             public_key_path=f"{key_manager.keys_path}/{active_key.key_id}_public.pem"
@@ -128,11 +129,18 @@ async def create_note_example():
         # logger.info(f"Activity: {to_json(create_activity, indent=2)}")
 
         # Sign the request
+        parsed_url = urlparse(inbox_url)
+        headers = {
+            "Accept": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
+            "Content-Type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
+            "User-Agent": "PyFed/1.0",
+            "Host": parsed_url.netloc
+        }
         logger.debug(f"Resolved host: {urlparse(inbox_url).netloc}")
         signed_headers = await signature_verifier.sign_request(
             method="POST",
             path="/inbox",
-            headers={"Host": urlparse(inbox_url).netloc},
+            headers=headers,
             body=activity_dict
         )
 
diff --git a/examples/send_to_mastodon.py b/examples/send_to_mastodon.py
new file mode 100644
index 0000000..962ff48
--- /dev/null
+++ b/examples/send_to_mastodon.py
@@ -0,0 +1,94 @@
+"""
+Example of sending a message to Mastodon following ActivityPub spec.
+"""
+
+import asyncio
+import logging
+from pathlib import Path
+import sys
+# Add src directory to Python path
+src_path = Path(__file__).parent.parent / "src"
+sys.path.insert(0, str(src_path))
+
+from pyfed.federation.delivery import ActivityDelivery
+from pyfed.federation.discovery import InstanceDiscovery
+from pyfed.security.key_management import KeyManager
+
+logging.basicConfig(level=logging.INFO)
+logger = logging.getLogger(__name__)
+
+async def send_activity_to_mastodon():
+    # Initialize components
+    key_manager = KeyManager(
+        domain="localhost:8000",
+        keys_path="example_keys"
+    )
+    
+    discovery = InstanceDiscovery()
+    delivery = ActivityDelivery(key_manager=key_manager)
+    
+    await discovery.initialize()
+    await delivery.initialize()
+    
+    try:
+        # 1. First, perform WebFinger lookup to get the actor's inbox
+        logger.info("Performing WebFinger lookup...")
+        webfinger_result = await discovery.webfinger(
+            resource="acct:kene29@mastodon.social"
+        )
+        logger.info(f"WebFinger result: {webfinger_result}")
+        
+        if not webfinger_result:
+            raise Exception("Could not find user through WebFinger")
+            
+        # Find ActivityPub actor URL from WebFinger result
+        actor_url = None
+        for link in webfinger_result.get('links', []):
+            if link.get('rel') == 'self' and link.get('type') == 'application/activity+json':
+                actor_url = link.get('href')
+                break
+                
+        if not actor_url:
+            raise Exception("Could not find ActivityPub actor URL")
+            
+        # 2. Create the Activity
+        note_activity = {
+            "@context": "https://www.w3.org/ns/activitystreams",
+            "type": "Create",
+            "actor": f"https://localhost:8000/users/testuser",
+            "object": {
+                "type": "Note",
+                "content": "Hello @kene29@mastodon.social! This is a test message from PyFed.",
+                "attributedTo": f"https://localhost:8000/users/testuser",
+                "to": [actor_url],
+                "cc": ["https://www.w3.org/ns/activitystreams#Public"]
+            },
+            "to": [actor_url],
+            "cc": ["https://www.w3.org/ns/activitystreams#Public"]
+        }
+        
+        # 3. Deliver the activity
+        logger.info(f"Delivering activity: {note_activity}")
+        result = await delivery.deliver_activity(
+            activity=note_activity,
+            recipients=[actor_url]
+        )
+        
+        if result.success:
+            logger.info("Activity delivered successfully!")
+            logger.info(f"Delivered to: {result.success}")
+        else:
+            logger.error("Activity delivery failed!")
+            logger.error(f"Failed recipients: {result.failed}")
+            logger.error(f"Error: {result.error_message}")
+            
+    except Exception as e:
+        logger.error(f"Error: {e}")
+        
+    finally:
+        # Clean up
+        await discovery.close()
+        await delivery.close()
+
+if __name__ == "__main__":
+    asyncio.run(send_activity_to_mastodon()) 
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index c8fb7e9..b8ed079 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -47,4 +47,5 @@ soupsieve==2.6
 SQLAlchemy==2.0.36
 starlette==0.41.2
 typing_extensions==4.12.2
-yarl==1.15.2
\ No newline at end of file
+yarl==1.15.2
+certifi
\ No newline at end of file
diff --git a/src/pyfed/.DS_Store b/src/pyfed/.DS_Store
index c6932cc849686664d20de6776ac3859c5ece9817..0366676234840b2cc46b344ce97f71b49bb555c8 100644
GIT binary patch
literal 8196
zcmZQzU|@7AO)+F(kYHe7;9!8z0^AH(0Z1N%F(jFwA}k>D7#IW?7!n!M8B&wViwlx+
zpmL+sXb6mkz-S1JhQMeDjE2DA3IRrlb2xC+`w-btax?@+LtsRP0H}OWfVAxy9H4Xq
zga%15FfuTJy8w&~44^ImIE)y<{Qw4#97rpO25AM+Agv6HAQspRuvP{}s8&XBHw2_l
z0BR0cI|C!wW{@~oI|C!wW(EdEh;{}>sLhPf9ttBwI|Cy`I|C!wcCeA7#ApbNh5%X!
zfU*xeLoq`MLq0<ha{oO)iGhJ(M^ZsfW^svu!8JxEW)@a9b`A~>ZjRXCjQsN8lEjkI
zVyDESXb>+XKR+i4#!gHM%S<hg7Z7pI&nrpH%u6i-DGAO@Nli*DiV4rmOUW;H$}i1J
zDF*9>N^o#;a>fftR973C8R;k(nHksWC{&wT8t5pP8kyGCa&m|&>strKXXoVR<#$1R
z0V@m`cp-c^+0DSffaa^5;xgx?9FVV=Uch{H1LiAEE^dk75U#L@%;fyM;LNJjcme+6
zjQn!X<ovvVqSUg?)bjA;{G8I<yyAEP!Q{l8<kFnPl2pf>9FXGTh?3Nb5)dn@IKQaG
z8LTi~K%lfZH7GSFu_UuB)g`ecwK!ft2xO9fK}lwQUU67zQE_H|p0a}h#MzwdctoY-
zl{IvY%xxT9z5K55^6?7@atU)qWu~O&m1L%6rWQpe7AL3XrDW!%hn8oilw^dLWfo^9
z<)nt>CFZ7b3T)%N!pqMmASNy$5mB0ySy-A`QYoO!!Er@^kDr%ILROBGLttx6N@7W>
zZ+;5Mz!b2IgoLaT2S+qW+_@+<u_P5D%q5|w&dDK=7*d>Bl?qnK#igyo$th4AlAW0c
zQ6s@+V93EKkP@1cn3S3WRwTh?YR17S;2mBD@gP*t%9?{ykWnWhIX^cyHLs)?A}nF=
zz`-GKA`WD?PhxS2e?e+qY6{pFPO=gbksxu$l$6vIkSF;0cqQB=JUIj|aYPkoBo?Lm
zm*=GxT@iqY`*LuAV=b{L6=afYN@htuTrv<ODUg|0mRXVr3VlfG<xqCefCN4?0712o
zB!d!z34;wo07E203PU+V55rW3X$(sl)-mj4ILvU8;UdE|hU*MB8E!E=W_ZK!j^RDS
z2Zo;vzZiZq{AJ`~6lN4*6lGLo)MC_T)M3<PG-kA6v}LqobYOI4^kejA3}6gmj9^S;
zOkzxCOl8bvEMqKZtYEBWY+>wW>|^X_oXEI<aV6ts#x0Cn8TT>nXFSMwi194rHOA|V
zHyB?rerEi__?3x+iIYi)Nt_8Zc*nrNzyz%l8Nwlcfsss7SaU%EG#A8T%LTG-?p($L
znhS37fYZH@u!yJ}C^MK@JHXQnB$dGu7IK0Gr%{xY4NjxrXhlz4V&W2#Qd}~!p_zFp
zsTH`glmI)YAfpb)6_9FqE=5@ZR!)Hw2v>zmO;&)7Q@|U-)s)qi6=32J*b3q5$r^A8
zFmnhba)R|5%W888FmgaR=3JIs0xX;Y#hh0p#U;eJxa{mXIR&-}Fo2ah$~wymfU*Mz
zdctJjV8EU%S2Ap3*uijs;S9q?hI<T;8D21aXJlYxV-#Q%W0Yf5U{qvOVN_+*V>Dni
zWHe?pVYFqmXLMk6VsvKo0Vl^m#vsOM##qKU#stPh#w^Ah#$3ic#&X6=#wx}d##+WU
z#&*UI#xBMl#;J_c8D}ugVw}x5hjA(6Do7%OCp}LHA4W2+#!Px;!9{sF`FZK!#ve@K
zC_R87zznfl0@VL^WnjQH{tr<#N{)uWXb23;5MX3+33hS9Q!a!0+Mxb)0@VAU1_!7+
z&Isz4L-c`^fV%de{sf4IcE`aAKw3d!;I23$0|Q9wzzqa|tJ=}}f3*IGCgu@J0RRZ6
B1VI1*

delta 145
zcmZp1XfcprU|?W$DortDU=UznVBlbY(2N`t3&ketDKaum4A?!{K!#;9zrYlJV^gDA
z9ffL3BLf`;6JxW<n*^LTmx@hd+}O~_vY4HNLy(yP3<S6t7#Lg`7#KM=7Jg@*%r9f9
b01{_pU|@pMEFhW*Vjsu}44dP5<}d>Qr%o5I

diff --git a/src/pyfed/federation/delivery.py b/src/pyfed/federation/delivery.py
index cc1e94e..edf76e0 100644
--- a/src/pyfed/federation/delivery.py
+++ b/src/pyfed/federation/delivery.py
@@ -127,8 +127,8 @@ class ActivityDelivery:
             # Prepare headers
             parsed_url = urlparse(inbox_url)
             headers = {
-                "Content-Type": "application/activity+json",
-                "Accept": "application/activity+json",
+                "Accept": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
+                "Content-Type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
                 "User-Agent": "PyFed/1.0",
                 "Host": parsed_url.netloc
             }
diff --git a/src/pyfed/federation/discovery.py b/src/pyfed/federation/discovery.py
index 85ed45b..1f70568 100644
--- a/src/pyfed/federation/discovery.py
+++ b/src/pyfed/federation/discovery.py
@@ -16,6 +16,8 @@ from urllib.parse import urlparse, urljoin
 from datetime import datetime
 import asyncio
 from dataclasses import dataclass
+import certifi
+import ssl
 
 from ..utils.exceptions import DiscoveryError
 from ..utils.logging import get_logger
@@ -59,12 +61,14 @@ class InstanceDiscovery:
 
     async def initialize(self) -> None:
         """Initialize HTTP session."""
+        ssl_context = ssl.create_default_context(cafile=certifi.where())
         self.session = aiohttp.ClientSession(
             timeout=aiohttp.ClientTimeout(total=self.timeout),
             headers={
                 "User-Agent": "PyFed/1.0",
                 "Accept": "application/activity+json"
-            }
+            },
+            connector=aiohttp.TCPConnector(ssl=ssl_context)
         )
 
     async def discover_instance(self, domain: str) -> InstanceInfo:
@@ -172,7 +176,7 @@ class InstanceDiscovery:
             ]
             
             headers = {
-                "Accept": "application/activity+json"
+                "Accept": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
             }
             
             for url in locations:
-- 
GitLab