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