diff --git a/examples/send_message.py b/examples/send_message.py new file mode 100644 index 0000000000000000000000000000000000000000..eb4f00493e234823d2734a457ca09b46a9303564 --- /dev/null +++ b/examples/send_message.py @@ -0,0 +1,148 @@ +""" +Example of sending a message to Mastodon following ActivityPub spec. +""" + +import asyncio +import logging +from pathlib import Path +import sys +from config import CONFIG +from datetime import datetime, timezone + +# Add src directory to Python path +src_path = Path(__file__).parent.parent / "src" +sys.path.insert(0, str(src_path)) + +from pyfed.models import APPerson, APNote, APCreate +from pyfed.federation.delivery import ActivityDelivery +from pyfed.federation.discovery import InstanceDiscovery +from pyfed.security.key_management import KeyManager + +logging.basicConfig(level=logging.DEBUG) # Set to DEBUG for more detailed logs +logger = logging.getLogger(__name__) + + # @gvelez17@mas.to +async def send_activity_to_mastodon(): + # Initialize components with config + key_manager = KeyManager( + domain=CONFIG["domain"], + keys_path=CONFIG["keys_path"], + rotation_config=False + ) + await key_manager.initialize() + + # Force generate a new key with proper format + # await key_manager.rotate_keys() + + active_key = await key_manager.get_active_key() + logger.debug(f"Using active key ID: {active_key.key_id}") + logger.debug(f"Key document URL: https://{CONFIG['domain']}/keys/{active_key.key_id.split('/')[-1]}") + + discovery = InstanceDiscovery() + await discovery.initialize() + + delivery = ActivityDelivery(key_manager=key_manager, discovery=discovery) + + try: + # 1. First, perform WebFinger lookup to get the actor's URL + 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. Fetch the actor's profile to get their inbox URL + logger.info(f"Fetching actor profile from {actor_url}") + async with discovery.session.get(actor_url) as response: + if response.status != 200: + raise Exception(f"Failed to fetch actor profile: {response.status}") + actor_data = await response.json() + + # Get the inbox URL from the actor's profile + inbox_url = actor_data.get('inbox') + if not inbox_url: + raise Exception("Could not find actor's inbox URL") + + logger.info(f"Found actor's inbox: {inbox_url}") + + # 3. Create the Activity with ngrok domain + note_id = f"https://{CONFIG['domain']}/notes/{int(asyncio.get_event_loop().time() * 1000)}" + + # Create actor + actor = APPerson( + id=f"https://{CONFIG['domain']}/users/{CONFIG['user']}", + name="Alice", + preferred_username="alice", + inbox="https://example.com/users/alice/inbox", + outbox="https://example.com/users/alice/outbox", + followers="https://example.com/users/alice/followers" + ) + + # Create note with string attributed_to + note = APNote( + id=note_id, + content=f"Hello @gvelez17@mas.to! This is a test note to test mention to Golda", + attributed_to=str(actor.id), # Convert URL to string + to=[inbox_url], + cc=["https://www.w3.org/ns/activitystreams#Public"], + published=datetime.utcnow().isoformat() + "Z", + url=str(note_id), + tag=[{ + "type": "Mention", + "href": actor_url, + "name": "@gvelez17@mas.to" + }] + ) + + # Create activity + create_activity = APCreate( + id=f"https://example.com/activities/{datetime.now(timezone.utc).timestamp()}", + actor=str(actor.id), # Convert URL to string + object=note, + to=note.to, + cc=note.cc, + published=datetime.utcnow().isoformat() + "Z", + ) + + # 4. Deliver the activity + logger.info("Delivering activity...") + result = await delivery.deliver_to_inbox( + activity=create_activity.serialize(), + inbox_url=inbox_url, + username=CONFIG["user"] + ) + logger.info(f"Delivery result: {result}") + + if result.success: + logger.info("Activity delivered successfully!") + logger.info(f"Successfully delivered to: {result.success}") + else: + logger.error("Activity delivery failed!") + if result.failed: + logger.error(f"Failed recipients: {result.failed}") + if result.error_message: + 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