Initial commit
This commit is contained in:
commit
e2db45168e
3 changed files with 336 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
__pycache__/
|
||||
*.pyc
|
1
__init__.py
Normal file
1
__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .activitypub import *
|
333
activitypub.py
Normal file
333
activitypub.py
Normal file
|
@ -0,0 +1,333 @@
|
|||
|
||||
import datetime
|
||||
import logging
|
||||
import json
|
||||
import os
|
||||
import urllib.parse
|
||||
import traceback
|
||||
|
||||
from pelican import signals
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
__version__ = '0.1'
|
||||
|
||||
pagination = 25
|
||||
|
||||
|
||||
def ap_article(generator, writer):
|
||||
|
||||
now = datetime.datetime.utcnow()
|
||||
|
||||
author = generator.settings['AUTHOR']
|
||||
domain = urllib.parse.urlparse(generator.settings['SITEURL']).netloc
|
||||
|
||||
wkhmpath = os.path.join(writer.output_path, '.well-known/host-meta')
|
||||
wknipath = os.path.join(writer.output_path, '.well-known/nodeinfo')
|
||||
nipath = os.path.join(writer.output_path, 'activitypub/nodeinfo')
|
||||
wfpath = os.path.join(writer.output_path, '.well-known/webfinger')
|
||||
awfpath = os.path.join(writer.output_path, '.well-known/_webfinger')
|
||||
authorpath = os.path.join(writer.output_path, 'activitypub/users')
|
||||
os.makedirs(os.path.join(writer.output_path, '.well-known'), exist_ok=True)
|
||||
os.makedirs(awfpath, exist_ok=True)
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/users'), exist_ok=True)
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/posts'), exist_ok=True)
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/tags'), exist_ok=True)
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/collections/inbox'), exist_ok=True)
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/collections/outbox'), exist_ok=True)
|
||||
for author, _ in generator.authors:
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/collections/outbox_page', author.slug), exist_ok=True)
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/collections/following'), exist_ok=True)
|
||||
os.makedirs(os.path.join(writer.output_path, 'activitypub/collections/followers'), exist_ok=True)
|
||||
wknodeinfo = {
|
||||
'links': [
|
||||
{
|
||||
'href': os.path.join(writer.site_url, 'activitypub/nodeinfo'),
|
||||
'rel': 'http://nodeinfo.diaspora.software/ns/schema/2.0'
|
||||
}
|
||||
]
|
||||
}
|
||||
nodeinfo = {
|
||||
'version': '2.0',
|
||||
'software': {
|
||||
'name': 'pelican-activitypub',
|
||||
'version': __version__
|
||||
},
|
||||
'protocols': ['activitypub'],
|
||||
'services': {
|
||||
'inbound': [],
|
||||
'outbound': ['atom1.0', 'rss2.0']
|
||||
},
|
||||
'openRegistrations': False,
|
||||
'usage': {
|
||||
'users': {
|
||||
'total': len(generator.authors),
|
||||
},
|
||||
'localPosts': len(generator.articles)
|
||||
},
|
||||
'metadata': {
|
||||
'nodeName': generator.settings['SITENAME'],
|
||||
}
|
||||
}
|
||||
wfurl = os.path.join(generator.settings['SITEURL'], '.well-known/webfinger?resource={uri}')
|
||||
hostmeta = f'<xml version="1.0" encoding="UTF-8"?><XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0"><Link rel="lrdd" template="{wfurl}" type="application/xrd+xml" /></XRD>'
|
||||
webfinger = {
|
||||
'subject': f'acct:{author}@{domain}',
|
||||
'aliases': [
|
||||
os.path.join(generator.settings['SITEURL'], 'author', author.slug + '.html'),
|
||||
os.path.join(generator.settings['SITEURL'], 'activitypub/users/', author.slug)
|
||||
],
|
||||
'links': [
|
||||
{
|
||||
'rel': 'http://webfinger.net/rel/profile-page',
|
||||
'type': 'text/html',
|
||||
'href': os.path.join(generator.settings['SITEURL'], 'author', author.slug + '.html')
|
||||
},
|
||||
{
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': os.path.join(generator.settings['SITEURL'], 'activitypub/users', author.slug)
|
||||
}
|
||||
]
|
||||
}
|
||||
with open(wkhmpath, 'w') as hf:
|
||||
hf.write(hostmeta)
|
||||
with open(wknipath, 'w') as nf:
|
||||
json.dump(wknodeinfo, nf)
|
||||
with open(nipath, 'w') as nf:
|
||||
json.dump(nodeinfo, nf)
|
||||
with open(wfpath, 'w') as wf:
|
||||
json.dump(webfinger, wf)
|
||||
|
||||
for t in generator.tags:
|
||||
url = os.path.join(generator.settings['SITEURL'], 'activitypub/tags', t.slug)
|
||||
path = os.path.join(writer.output_path, 'activitypub/tags', t.slug)
|
||||
articles = []
|
||||
for article in generator.articles:
|
||||
if t.name not in article.tags:
|
||||
continue
|
||||
articles.append(
|
||||
os.path.join(generator.settings['SITEURL'], 'activitypub/posts', article.slug)
|
||||
)
|
||||
tag = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'id': url,
|
||||
'type': 'OrderedCollection',
|
||||
'totalItems': len(articles),
|
||||
'orderedItems': articles
|
||||
}
|
||||
with open(path, 'w') as f:
|
||||
json.dump(tag, f)
|
||||
|
||||
articlemap = {}
|
||||
for article in generator.articles:
|
||||
aurl = os.path.join(generator.settings['SITEURL'], 'activitypub/posts', article.slug)
|
||||
apath = os.path.join(writer.output_path, 'activitypub/posts', article.slug)
|
||||
tags = []
|
||||
for tag in article.tags:
|
||||
tags.append({
|
||||
'type': 'Hashtag',
|
||||
'name': '#' + tag.slug,
|
||||
'href': os.path.join(generator.settings['SITEURL'], 'activitypub/tags', tag.slug)
|
||||
})
|
||||
replyto = None
|
||||
if 'series' in article.metadata and 'seriesindex' in article.metadata:
|
||||
series = article.metadata['series']
|
||||
sindex = int(article.metadata['seriesindex'])
|
||||
for sa in generator.articles:
|
||||
if 'series' in sa.metadata and 'seriesindex' in sa.metadata \
|
||||
and sa.metadata['series'] == series and int(sa.metadata['seriesindex']) == sindex - 1:
|
||||
replyto = os.path.join(generator.settings['SITEURL'], 'activitypub/posts', sa.slug)
|
||||
break
|
||||
cc = [os.path.join(generator.settings['SITEURL'], 'activitypub/collections/followers', article.author.slug)]
|
||||
aa = generator.settings.get('ACTIVITYPUB_AUTHORS', {}).get(author.name, {})
|
||||
if 'movedTo' in aa:
|
||||
cc.append(aa['movedTo'])
|
||||
tags.append({
|
||||
'type': 'Mention',
|
||||
'href': aa['movedTo'],
|
||||
'name': aa['_movedTo_name']
|
||||
})
|
||||
cmap = {}
|
||||
tmap = {}
|
||||
for lang in article.translations + [article]:
|
||||
tmap[lang.lang] = lang.title
|
||||
cmap[lang.lang] = lang.content
|
||||
articlemap[article.slug] = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'Article',
|
||||
'id': aurl,
|
||||
'published': article.date.isoformat(timespec='seconds'),
|
||||
'inReplyTo': replyto,
|
||||
'url': os.path.join(generator.settings['SITEURL'], article.url),
|
||||
'attributedTo': os.path.join(generator.settings['SITEURL'], 'activitypub/users', article.author.slug),
|
||||
'to': ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
'cc': cc,
|
||||
'name': article.title,
|
||||
'nameMap': tmap,
|
||||
'content': article.content,
|
||||
'contentMap': cmap,
|
||||
'summary': None,
|
||||
'attachment': [],
|
||||
'tag': tags
|
||||
}
|
||||
with open(apath, 'w') as f:
|
||||
json.dump(articlemap[article.slug], f)
|
||||
|
||||
for author, articles in generator.authors:
|
||||
aa = generator.settings.get('ACTIVITYPUB_AUTHORS', {}).get(author.name, {})
|
||||
url = os.path.join(generator.settings['SITEURL'], 'activitypub/users', author.slug)
|
||||
wwwurl = os.path.join(generator.settings['SITEURL'], 'author', author.slug + '.html')
|
||||
inboxurl = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/inbox', author.slug)
|
||||
inboxpath = os.path.join(writer.output_path, 'activitypub/collections/inbox', author.slug)
|
||||
outboxurl = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox', author.slug)
|
||||
outboxpath = os.path.join(writer.output_path, 'activitypub/collections/outbox', author.slug)
|
||||
followersurl = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/followers', author.slug)
|
||||
followerspath = os.path.join(writer.output_path, 'activitypub/collections/followers', author.slug)
|
||||
followingurl = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/following', author.slug)
|
||||
followingpath = os.path.join(writer.output_path, 'activitypub/collections/following', author.slug)
|
||||
|
||||
creates = []
|
||||
for article in articles:
|
||||
art = articlemap[article.slug]
|
||||
creates.append({
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'Create',
|
||||
'id': os.path.join(generator.settings['SITEURL'], 'activitypub/posts', article.slug),
|
||||
'actor': url,
|
||||
'published': article.date.isoformat(timespec='seconds'),
|
||||
'to': art['to'],
|
||||
'cc': art['cc'],
|
||||
'object': art
|
||||
})
|
||||
|
||||
if len(articles) == 0:
|
||||
published = now.isoformat(timespec='seconds') + 'Z'
|
||||
else:
|
||||
published = min([x.date for x in articles]).isoformat(timespec='seconds')
|
||||
a = {
|
||||
'@context': [
|
||||
'https://www.w3.org/ns/activitystreams',
|
||||
{
|
||||
'schema': 'http://schema.org#',
|
||||
'toot': 'http://joinmastodon.org/ns#',
|
||||
'PropertyValue': 'schema:PropertyValue',
|
||||
'value': 'schema:value',
|
||||
'alsoKnownAs': {
|
||||
'@id': 'as:alsoKnownAs',
|
||||
'@type': '@id'
|
||||
},
|
||||
'movedTo': {
|
||||
'@id': 'as:movedTo',
|
||||
'@type': '@id'
|
||||
},
|
||||
'discoverable': 'toot:discoverable',
|
||||
}
|
||||
],
|
||||
'type': 'Person',
|
||||
'id': url,
|
||||
'inbox': inboxurl,
|
||||
'outbox': outboxurl,
|
||||
'following': followingurl,
|
||||
'followers': followersurl,
|
||||
'preferredUsername': author.name,
|
||||
'url': wwwurl,
|
||||
'name': author.slug,
|
||||
'discoverable': True,
|
||||
'manuallyApprovesFollowers': True,
|
||||
'published': published,
|
||||
'updated': now.isoformat(timespec='seconds') + 'Z',
|
||||
'tag': [],
|
||||
'attachment': [],
|
||||
'endpoints': {
|
||||
'sharedInbox': inboxurl
|
||||
},
|
||||
'icon': {},
|
||||
'image': {}
|
||||
}
|
||||
a.update({k: v for k, v in aa.items() if not k.startswith('_')})
|
||||
inbox = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'OrderedCollection',
|
||||
'id': inboxurl,
|
||||
'totalItems': 0,
|
||||
'orderedItems': []
|
||||
}
|
||||
maxpage = len(creates) // pagination
|
||||
outbox = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'OrderedCollection',
|
||||
'id': outboxurl,
|
||||
'totalItems': len(creates),
|
||||
'first': os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page/', author.slug, str(0)),
|
||||
'last': os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page/', author.slug, str(maxpage))
|
||||
}
|
||||
following = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'OrderedCollection',
|
||||
'id': followingurl,
|
||||
'totalItems': 0,
|
||||
'orderedItems': []
|
||||
}
|
||||
followers = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'OrderedCollection',
|
||||
'id': followersurl,
|
||||
'totalItems': 0,
|
||||
'orderedItems': []
|
||||
}
|
||||
for i in range(0, len(creates), pagination):
|
||||
ipage = i // pagination
|
||||
outpageurl = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page', author.slug, str(ipage))
|
||||
outpagepath = os.path.join(writer.output_path, 'activitypub/collections/outbox_page', author.slug, str(ipage))
|
||||
page = {
|
||||
'@context': ['https://www.w3.org/ns/activitystreams'],
|
||||
'type': 'OrderedCollectionPage',
|
||||
'id': outpageurl,
|
||||
'totalItems': len(creates),
|
||||
'partOf': outboxurl,
|
||||
'orderedItems': creates[i:i+pagination]
|
||||
}
|
||||
if ipage > 0:
|
||||
page['prev'] = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page', author.slug, str(ipage-1))
|
||||
if ipage < maxpage:
|
||||
page['next'] = os.path.join(generator.settings['SITEURL'], 'activitypub/collections/outbox_page', author.slug, str(ipage+1))
|
||||
with open(outpagepath, 'w') as f:
|
||||
json.dump(page, f)
|
||||
author_webfinger = {
|
||||
'subject': f'acct:{author.name}@{domain}',
|
||||
'aliases': [
|
||||
os.path.join(generator.settings['SITEURL'], 'author', author.name + '.html'),
|
||||
os.path.join(generator.settings['SITEURL'], 'activitypub/users/', author.name)
|
||||
],
|
||||
'links': [
|
||||
{
|
||||
'rel': 'http://webfinger.net/rel/profile-page',
|
||||
'type': 'text/html',
|
||||
'href': os.path.join(generator.settings['SITEURL'], 'author', author.name + '.html')
|
||||
},
|
||||
{
|
||||
'rel': 'self',
|
||||
'type': 'application/activity+json',
|
||||
'href': os.path.join(generator.settings['SITEURL'], 'activitypub/users', author.name)
|
||||
}
|
||||
]
|
||||
}
|
||||
with open(os.path.join(awfpath, author.name), 'w') as f:
|
||||
json.dump(author_webfinger, f)
|
||||
with open(os.path.join(authorpath, author.name), 'w') as f:
|
||||
json.dump(a, f)
|
||||
with open(inboxpath, 'w') as f:
|
||||
json.dump(inbox, f)
|
||||
with open(outboxpath, 'w') as f:
|
||||
json.dump(outbox, f)
|
||||
with open(followingpath, 'w') as f:
|
||||
json.dump(following, f)
|
||||
with open(followerspath, 'w') as f:
|
||||
json.dump(followers, f)
|
||||
|
||||
|
||||
def register():
|
||||
signals.article_writer_finalized.connect(ap_article)
|
Loading…
Reference in a new issue