Skip to content
Snippets Groups Projects
Commit 3a940348 authored by Kenechukwu Orjiene's avatar Kenechukwu Orjiene
Browse files

Merge branch 'feat/dev' into 'main'

working on the delivey and discory to enable sending of activities"

See merge request !10
parents 5fc42136 6da4dad2
No related branches found
No related tags found
1 merge request!10working on the delivey and discory to enable sending of activities"
...@@ -65,6 +65,7 @@ async def create_note_example(): ...@@ -65,6 +65,7 @@ async def create_note_example():
logger.info(f"Active key: {active_key}") logger.info(f"Active key: {active_key}")
# Initialize HTTP signature verifier with the active key paths # 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( signature_verifier = HTTPSignatureVerifier(
private_key_path=f"{key_manager.keys_path}/{active_key.key_id}_private.pem", 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" public_key_path=f"{key_manager.keys_path}/{active_key.key_id}_public.pem"
...@@ -128,11 +129,18 @@ async def create_note_example(): ...@@ -128,11 +129,18 @@ async def create_note_example():
# logger.info(f"Activity: {to_json(create_activity, indent=2)}") # logger.info(f"Activity: {to_json(create_activity, indent=2)}")
# Sign the request # 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}") logger.debug(f"Resolved host: {urlparse(inbox_url).netloc}")
signed_headers = await signature_verifier.sign_request( signed_headers = await signature_verifier.sign_request(
method="POST", method="POST",
path="/inbox", path="/inbox",
headers={"Host": urlparse(inbox_url).netloc}, headers=headers,
body=activity_dict body=activity_dict
) )
......
"""
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
...@@ -48,3 +48,4 @@ SQLAlchemy==2.0.36 ...@@ -48,3 +48,4 @@ SQLAlchemy==2.0.36
starlette==0.41.2 starlette==0.41.2
typing_extensions==4.12.2 typing_extensions==4.12.2
yarl==1.15.2 yarl==1.15.2
certifi
\ No newline at end of file
No preview for this file type
...@@ -127,8 +127,8 @@ class ActivityDelivery: ...@@ -127,8 +127,8 @@ class ActivityDelivery:
# Prepare headers # Prepare headers
parsed_url = urlparse(inbox_url) parsed_url = urlparse(inbox_url)
headers = { headers = {
"Content-Type": "application/activity+json", "Accept": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"Accept": "application/activity+json", "Content-Type": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"",
"User-Agent": "PyFed/1.0", "User-Agent": "PyFed/1.0",
"Host": parsed_url.netloc "Host": parsed_url.netloc
} }
......
...@@ -16,6 +16,8 @@ from urllib.parse import urlparse, urljoin ...@@ -16,6 +16,8 @@ from urllib.parse import urlparse, urljoin
from datetime import datetime from datetime import datetime
import asyncio import asyncio
from dataclasses import dataclass from dataclasses import dataclass
import certifi
import ssl
from ..utils.exceptions import DiscoveryError from ..utils.exceptions import DiscoveryError
from ..utils.logging import get_logger from ..utils.logging import get_logger
...@@ -59,12 +61,14 @@ class InstanceDiscovery: ...@@ -59,12 +61,14 @@ class InstanceDiscovery:
async def initialize(self) -> None: async def initialize(self) -> None:
"""Initialize HTTP session.""" """Initialize HTTP session."""
ssl_context = ssl.create_default_context(cafile=certifi.where())
self.session = aiohttp.ClientSession( self.session = aiohttp.ClientSession(
timeout=aiohttp.ClientTimeout(total=self.timeout), timeout=aiohttp.ClientTimeout(total=self.timeout),
headers={ headers={
"User-Agent": "PyFed/1.0", "User-Agent": "PyFed/1.0",
"Accept": "application/activity+json" "Accept": "application/activity+json"
} },
connector=aiohttp.TCPConnector(ssl=ssl_context)
) )
async def discover_instance(self, domain: str) -> InstanceInfo: async def discover_instance(self, domain: str) -> InstanceInfo:
...@@ -172,7 +176,7 @@ class InstanceDiscovery: ...@@ -172,7 +176,7 @@ class InstanceDiscovery:
] ]
headers = { headers = {
"Accept": "application/activity+json" "Accept": "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
} }
for url in locations: for url in locations:
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment