From b508abaf9d34f9e08eb61bf2e413807c4ad3f5bb Mon Sep 17 00:00:00 2001 From: Bob Mottram Date: Sat, 29 Jun 2019 11:08:59 +0100 Subject: [PATCH] Creating posts --- README.md | 2 ++ epicyon.py | 9 ++++--- inbox.py | 44 ++++++++++++++++++++++++++++++++++ posts.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++-------- 4 files changed, 112 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 99617c003..126a09660 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Some experiments with ActivityPub in Python. +Based on the specification: https://www.w3.org/TR/activitypub + ## Install ``` bash diff --git a/epicyon.py b/epicyon.py index c1165ee91..05dc78d93 100644 --- a/epicyon.py +++ b/epicyon.py @@ -11,6 +11,7 @@ from person import setPreferredUsername from person import setBio from webfinger import webfingerHandle from posts import getUserPosts +from posts import createPublicPost from session import createSession import json import sys @@ -32,10 +33,12 @@ session = createSession(useTor) privateKeyPem,publicKeyPem,person,wfEndpoint=createPerson(username,domain,https,True) setPreferredUsername(username,domain,'badger') setBio(username,domain,'Some personal info') -runDaemon(domain,port,federationList,useTor) +createPublicPost(username, domain, https, "G'day world!", False, True) + +#runDaemon(domain,port,federationList,useTor) #testHttpsig() -#sys.exit() +sys.exit() #pprint(person) #print('\n') @@ -53,4 +56,4 @@ maxMentions=10 maxEmoji=10 maxAttachments=5 userPosts = getUserPosts(session,wfRequest,2,maxMentions,maxEmoji,maxAttachments,federationList) -print(str(userPosts)) +#print(str(userPosts)) diff --git a/inbox.py b/inbox.py index c8c7ea902..a4a88884a 100644 --- a/inbox.py +++ b/inbox.py @@ -8,6 +8,7 @@ __status__ = "Production" import json import os +import datetime def inboxPermittedMessage(messageJson,federationList) -> bool: """ check that we are receiving from a permitted domain @@ -40,3 +41,46 @@ def inboxPermittedMessage(messageJson,federationList) -> bool: return False return True + +def receivePublicMessage(message) -> bool: + print("TODO") + +def validPublishedDate(published): + currTime=datetime.datetime.utcnow() + pubDate=datetime.datetime.strptime(published,"%Y-%m-%dT%H:%M:%SZ") + daysSincePublished = (currTime - pubTime).days + if daysSincePublished>30: + return False + return True + +def receiveMessage(message): + if not message.get('type'): + return + if message['type']!='Create': + return + if not message.get('published'): + return + # is the message too old? + if not validPublishedDate(message['published']): + return + if not message.get('to'): + return + if not message.get('id'): + return + for recipient in message['to']: + if recipient.endswith('/activitystreams#Public'): + receivePublicMessage(message) + continue + + username='' + domain='' + messageId=message['id'].replace('/','_') + handle=username.lower()+'@'+domain.lower() + baseDir=os.getcwd() + if not os.path.isdir(baseDir+'/accounts/'+handle): + os.mkdir(baseDir+'/accounts/'+handle) + if not os.path.isdir(baseDir+'/accounts/'+handle+'/inbox'): + os.mkdir(baseDir+'/accounts/'+handle+'/inbox') + filename=baseDir+'/accounts/'+handle+'/inbox/'+messageId+'.json' + with open(filename, 'w') as fp: + commentjson.dump(personJson, fp, indent=4, sort_keys=False) diff --git a/posts.py b/posts.py index 7fcdcbc48..01ab76740 100644 --- a/posts.py +++ b/posts.py @@ -8,7 +8,11 @@ __status__ = "Production" import requests import json +import commentjson import html +import datetime +import os +from pprint import pprint from random import randint from session import getJson try: @@ -37,17 +41,18 @@ def getUserUrl(wfRequest) -> str: return None def parseUserFeed(session,feedUrl,asHeader) -> None: - feed = getJson(session,feedUrl,asHeader,None) + feedJson = getJson(session,feedUrl,asHeader,None) + pprint(feedJson) - if 'orderedItems' in feed: + if 'orderedItems' in feedJson: for item in feed['orderedItems']: yield item nextUrl = None - if 'first' in feed: + if 'first' in feedJson: nextUrl = feed['first'] - elif 'next' in feed: - nextUrl = feed['next'] + elif 'next' in feedJson: + nextUrl = feedJson['next'] if nextUrl: for item in parseUserFeed(session,nextUrl,asHeader): @@ -149,28 +154,45 @@ def getUserPosts(session,wfRequest,maxPosts,maxMentions,maxEmoji,maxAttachments, break return userPosts -def createPublicPost(username: str, domain: str, https: bool, content: str, followersOnly: bool) -> {}: +def createOutboxDir(username: str,domain: str) -> (str,str): + """Create an outbox for a person and returns the feed filename and directory + """ + handle=username.lower()+'@'+domain.lower() + baseDir=os.getcwd() + if not os.path.isdir(baseDir+'/accounts/'+handle): + os.mkdir(baseDir+'/accounts/'+handle) + outboxDir=baseDir+'/accounts/'+handle+'/outbox' + if not os.path.isdir(outboxDir): + os.mkdir(outboxDir) + outboxJsonFilename=baseDir+'/accounts/'+handle+'/outbox.json' + return outboxJsonFilename,outboxDir + +def createPublicPost(username: str, domain: str, https: bool, content: str, followersOnly: bool, saveToFile: bool) -> {}: + """Creates a post + """ prefix='https' if not https: prefix='http' - statusNumber=str(randint(100000000000000000,999999999999999999)) currTime=datetime.datetime.utcnow() + daysSinceEpoch=(currTime - datetime.datetime(1970,1,1)).days + statusNumber=str((daysSinceEpoch*24*60*60) + (currTime.hour*60*60) + (currTime.minute*60) + currTime.second) published=currTime.strftime("%Y-%m-%dT%H:%M:%SZ") conversationDate=currTime.strftime("%Y-%m-%d") - conversationId=str(randint(100000000,999999999)) + conversationId=statusNumber postTo='https://www.w3.org/ns/activitystreams#Public' postCC=prefix+'://'+domain+'/users/'+username+'/followers' if followersOnly: postTo=postCC postCC='' + newPostId=prefix+'://'+domain+'/users/'+username+'/statuses/'+statusNumber newPost = { - 'id': prefix+'://'+domain+'/users/'+username+'/statuses/'+statusNumber+'/activity', + 'id': newPostId+'/activity', 'type': 'Create', 'actor': prefix+'://'+domain+'/users/'+username, 'published': published, 'to': ['https://www.w3.org/ns/activitystreams#Public'], 'cc': [prefix+'://'+domain+'/users/'+username+'/followers'], - 'object': {'id': prefix+'://'+domain+'/users/'+username+'/statuses/'+statusNumber, + 'object': {'id': newPostId, 'type': 'Note', 'summary': None, 'inReplyTo': None, @@ -200,4 +222,32 @@ def createPublicPost(username: str, domain: str, https: bool, content: str, foll #} } } + if saveToFile: + outboxJsonFilename,outboxDir = createOutboxDir(username,domain) + filename=outboxDir+'/'+newPostId.replace('/','#')+'.json' + with open(filename, 'w') as fp: + commentjson.dump(newPost, fp, indent=4, sort_keys=False) return newPost + +def createOutbox(username: str,domain: str,https: bool,noOfItems: int): + prefix='https' + if not https: + prefix='http' + outboxJsonFilename,outboxDir = createOutboxDir(username,domain) + outboxItems=0 + outboxHeader = {'@context': 'https://www.w3.org/ns/activitystreams', + 'first': prefix+'://'+domain+'/users/'+username+'/outbox?page=true', + 'id': prefix+'://'+domain+'/users/'+username+'/outbox', + 'last': prefix+'://'+domain+'/users/'+username+'/outbox?min_id=0&page=true', + 'totalItems': str(outboxItems), + 'type': 'OrderedCollection'} + maxMessageId=100000000000000000 + minMessageId=100000000000000000 + outboxItems = {'@context': 'https://www.w3.org/ns/activitystreams', + 'id': prefix+'://'+domain+'/users/'+username+'/outbox?page=true', + 'next': prefix+'://'+domain+'/users/'+username+'/outbox?max_id='+str(maxMessageId)+'&page=true', + 'orderedItems': [ + ], + 'partOf': prefix+'://'+domain+'/users/'+username+'/outbox', + 'prev': prefix+'://'+domain+'/users/'+username+'/outbox?min_id='+str(minMessageId)+'&page=true', + 'type': 'OrderedCollectionPage'}