summaryrefslogtreecommitdiffstats
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/apps.py6
-rw-r--r--core/ld.py302
2 files changed, 308 insertions, 0 deletions
diff --git a/core/apps.py b/core/apps.py
index c0ce093..6098f6b 100644
--- a/core/apps.py
+++ b/core/apps.py
@@ -1,6 +1,12 @@
from django.apps import AppConfig
+from pyld import jsonld
+
+from core.ld import builtin_document_loader
class CoreConfig(AppConfig):
default_auto_field = "django.db.models.BigAutoField"
name = "core"
+
+ def ready(self) -> None:
+ jsonld.set_document_loader(builtin_document_loader)
diff --git a/core/ld.py b/core/ld.py
new file mode 100644
index 0000000..7d4167c
--- /dev/null
+++ b/core/ld.py
@@ -0,0 +1,302 @@
+import urllib.parse as urllib_parse
+
+from pyld import jsonld
+from pyld.jsonld import JsonLdError
+
+schemas = {
+ "www.w3.org/ns/activitystreams": {
+ "contentType": "application/ld+json",
+ "documentUrl": "https://www.w3.org/ns/activitystreams",
+ "contextUrl": None,
+ "document": {
+ "@context": {
+ "@vocab": "_:",
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
+ "as": "https://www.w3.org/ns/activitystreams#",
+ "ldp": "http://www.w3.org/ns/ldp#",
+ "vcard": "http://www.w3.org/2006/vcard/ns#",
+ "id": "@id",
+ "type": "@type",
+ "Accept": "as:Accept",
+ "Activity": "as:Activity",
+ "IntransitiveActivity": "as:IntransitiveActivity",
+ "Add": "as:Add",
+ "Announce": "as:Announce",
+ "Application": "as:Application",
+ "Arrive": "as:Arrive",
+ "Article": "as:Article",
+ "Audio": "as:Audio",
+ "Block": "as:Block",
+ "Collection": "as:Collection",
+ "CollectionPage": "as:CollectionPage",
+ "Relationship": "as:Relationship",
+ "Create": "as:Create",
+ "Delete": "as:Delete",
+ "Dislike": "as:Dislike",
+ "Document": "as:Document",
+ "Event": "as:Event",
+ "Follow": "as:Follow",
+ "Flag": "as:Flag",
+ "Group": "as:Group",
+ "Ignore": "as:Ignore",
+ "Image": "as:Image",
+ "Invite": "as:Invite",
+ "Join": "as:Join",
+ "Leave": "as:Leave",
+ "Like": "as:Like",
+ "Link": "as:Link",
+ "Mention": "as:Mention",
+ "Note": "as:Note",
+ "Object": "as:Object",
+ "Offer": "as:Offer",
+ "OrderedCollection": "as:OrderedCollection",
+ "OrderedCollectionPage": "as:OrderedCollectionPage",
+ "Organization": "as:Organization",
+ "Page": "as:Page",
+ "Person": "as:Person",
+ "Place": "as:Place",
+ "Profile": "as:Profile",
+ "Question": "as:Question",
+ "Reject": "as:Reject",
+ "Remove": "as:Remove",
+ "Service": "as:Service",
+ "TentativeAccept": "as:TentativeAccept",
+ "TentativeReject": "as:TentativeReject",
+ "Tombstone": "as:Tombstone",
+ "Undo": "as:Undo",
+ "Update": "as:Update",
+ "Video": "as:Video",
+ "View": "as:View",
+ "Listen": "as:Listen",
+ "Read": "as:Read",
+ "Move": "as:Move",
+ "Travel": "as:Travel",
+ "IsFollowing": "as:IsFollowing",
+ "IsFollowedBy": "as:IsFollowedBy",
+ "IsContact": "as:IsContact",
+ "IsMember": "as:IsMember",
+ "subject": {"@id": "as:subject", "@type": "@id"},
+ "relationship": {"@id": "as:relationship", "@type": "@id"},
+ "actor": {"@id": "as:actor", "@type": "@id"},
+ "attributedTo": {"@id": "as:attributedTo", "@type": "@id"},
+ "attachment": {"@id": "as:attachment", "@type": "@id"},
+ "bcc": {"@id": "as:bcc", "@type": "@id"},
+ "bto": {"@id": "as:bto", "@type": "@id"},
+ "cc": {"@id": "as:cc", "@type": "@id"},
+ "context": {"@id": "as:context", "@type": "@id"},
+ "current": {"@id": "as:current", "@type": "@id"},
+ "first": {"@id": "as:first", "@type": "@id"},
+ "generator": {"@id": "as:generator", "@type": "@id"},
+ "icon": {"@id": "as:icon", "@type": "@id"},
+ "image": {"@id": "as:image", "@type": "@id"},
+ "inReplyTo": {"@id": "as:inReplyTo", "@type": "@id"},
+ "items": {"@id": "as:items", "@type": "@id"},
+ "instrument": {"@id": "as:instrument", "@type": "@id"},
+ "orderedItems": {
+ "@id": "as:items",
+ "@type": "@id",
+ "@container": "@list",
+ },
+ "last": {"@id": "as:last", "@type": "@id"},
+ "location": {"@id": "as:location", "@type": "@id"},
+ "next": {"@id": "as:next", "@type": "@id"},
+ "object": {"@id": "as:object", "@type": "@id"},
+ "oneOf": {"@id": "as:oneOf", "@type": "@id"},
+ "anyOf": {"@id": "as:anyOf", "@type": "@id"},
+ "closed": {"@id": "as:closed", "@type": "xsd:dateTime"},
+ "origin": {"@id": "as:origin", "@type": "@id"},
+ "accuracy": {"@id": "as:accuracy", "@type": "xsd:float"},
+ "prev": {"@id": "as:prev", "@type": "@id"},
+ "preview": {"@id": "as:preview", "@type": "@id"},
+ "replies": {"@id": "as:replies", "@type": "@id"},
+ "result": {"@id": "as:result", "@type": "@id"},
+ "audience": {"@id": "as:audience", "@type": "@id"},
+ "partOf": {"@id": "as:partOf", "@type": "@id"},
+ "tag": {"@id": "as:tag", "@type": "@id"},
+ "target": {"@id": "as:target", "@type": "@id"},
+ "to": {"@id": "as:to", "@type": "@id"},
+ "url": {"@id": "as:url", "@type": "@id"},
+ "altitude": {"@id": "as:altitude", "@type": "xsd:float"},
+ "content": "as:content",
+ "contentMap": {"@id": "as:content", "@container": "@language"},
+ "name": "as:name",
+ "nameMap": {"@id": "as:name", "@container": "@language"},
+ "duration": {"@id": "as:duration", "@type": "xsd:duration"},
+ "endTime": {"@id": "as:endTime", "@type": "xsd:dateTime"},
+ "height": {"@id": "as:height", "@type": "xsd:nonNegativeInteger"},
+ "href": {"@id": "as:href", "@type": "@id"},
+ "hreflang": "as:hreflang",
+ "latitude": {"@id": "as:latitude", "@type": "xsd:float"},
+ "longitude": {"@id": "as:longitude", "@type": "xsd:float"},
+ "mediaType": "as:mediaType",
+ "published": {"@id": "as:published", "@type": "xsd:dateTime"},
+ "radius": {"@id": "as:radius", "@type": "xsd:float"},
+ "rel": "as:rel",
+ "startIndex": {
+ "@id": "as:startIndex",
+ "@type": "xsd:nonNegativeInteger",
+ },
+ "startTime": {"@id": "as:startTime", "@type": "xsd:dateTime"},
+ "summary": "as:summary",
+ "summaryMap": {"@id": "as:summary", "@container": "@language"},
+ "totalItems": {
+ "@id": "as:totalItems",
+ "@type": "xsd:nonNegativeInteger",
+ },
+ "units": "as:units",
+ "updated": {"@id": "as:updated", "@type": "xsd:dateTime"},
+ "width": {"@id": "as:width", "@type": "xsd:nonNegativeInteger"},
+ "describes": {"@id": "as:describes", "@type": "@id"},
+ "formerType": {"@id": "as:formerType", "@type": "@id"},
+ "deleted": {"@id": "as:deleted", "@type": "xsd:dateTime"},
+ "inbox": {"@id": "ldp:inbox", "@type": "@id"},
+ "outbox": {"@id": "as:outbox", "@type": "@id"},
+ "following": {"@id": "as:following", "@type": "@id"},
+ "followers": {"@id": "as:followers", "@type": "@id"},
+ "streams": {"@id": "as:streams", "@type": "@id"},
+ "preferredUsername": "as:preferredUsername",
+ "endpoints": {"@id": "as:endpoints", "@type": "@id"},
+ "uploadMedia": {"@id": "as:uploadMedia", "@type": "@id"},
+ "proxyUrl": {"@id": "as:proxyUrl", "@type": "@id"},
+ "liked": {"@id": "as:liked", "@type": "@id"},
+ "oauthAuthorizationEndpoint": {
+ "@id": "as:oauthAuthorizationEndpoint",
+ "@type": "@id",
+ },
+ "oauthTokenEndpoint": {"@id": "as:oauthTokenEndpoint", "@type": "@id"},
+ "provideClientKey": {"@id": "as:provideClientKey", "@type": "@id"},
+ "signClientKey": {"@id": "as:signClientKey", "@type": "@id"},
+ "sharedInbox": {"@id": "as:sharedInbox", "@type": "@id"},
+ "Public": {"@id": "as:Public", "@type": "@id"},
+ "source": "as:source",
+ "likes": {"@id": "as:likes", "@type": "@id"},
+ "shares": {"@id": "as:shares", "@type": "@id"},
+ "alsoKnownAs": {"@id": "as:alsoKnownAs", "@type": "@id"},
+ }
+ },
+ },
+ "w3id.org/security/v1": {
+ "contentType": "application/ld+json",
+ "documentUrl": "https://w3id.org/security/v1",
+ "contextUrl": None,
+ "document": {
+ "@context": {
+ "id": "@id",
+ "type": "@type",
+ "dc": "http://purl.org/dc/terms/",
+ "sec": "https://w3id.org/security#",
+ "xsd": "http://www.w3.org/2001/XMLSchema#",
+ "EcdsaKoblitzSignature2016": "sec:EcdsaKoblitzSignature2016",
+ "Ed25519Signature2018": "sec:Ed25519Signature2018",
+ "EncryptedMessage": "sec:EncryptedMessage",
+ "GraphSignature2012": "sec:GraphSignature2012",
+ "LinkedDataSignature2015": "sec:LinkedDataSignature2015",
+ "LinkedDataSignature2016": "sec:LinkedDataSignature2016",
+ "CryptographicKey": "sec:Key",
+ "authenticationTag": "sec:authenticationTag",
+ "canonicalizationAlgorithm": "sec:canonicalizationAlgorithm",
+ "cipherAlgorithm": "sec:cipherAlgorithm",
+ "cipherData": "sec:cipherData",
+ "cipherKey": "sec:cipherKey",
+ "created": {"@id": "dc:created", "@type": "xsd:dateTime"},
+ "creator": {"@id": "dc:creator", "@type": "@id"},
+ "digestAlgorithm": "sec:digestAlgorithm",
+ "digestValue": "sec:digestValue",
+ "domain": "sec:domain",
+ "encryptionKey": "sec:encryptionKey",
+ "expiration": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
+ "expires": {"@id": "sec:expiration", "@type": "xsd:dateTime"},
+ "initializationVector": "sec:initializationVector",
+ "iterationCount": "sec:iterationCount",
+ "nonce": "sec:nonce",
+ "normalizationAlgorithm": "sec:normalizationAlgorithm",
+ "owner": {"@id": "sec:owner", "@type": "@id"},
+ "password": "sec:password",
+ "privateKey": {"@id": "sec:privateKey", "@type": "@id"},
+ "privateKeyPem": "sec:privateKeyPem",
+ "publicKey": {"@id": "sec:publicKey", "@type": "@id"},
+ "publicKeyBase58": "sec:publicKeyBase58",
+ "publicKeyPem": "sec:publicKeyPem",
+ "publicKeyWif": "sec:publicKeyWif",
+ "publicKeyService": {"@id": "sec:publicKeyService", "@type": "@id"},
+ "revoked": {"@id": "sec:revoked", "@type": "xsd:dateTime"},
+ "salt": "sec:salt",
+ "signature": "sec:signature",
+ "signatureAlgorithm": "sec:signingAlgorithm",
+ "signatureValue": "sec:signatureValue",
+ }
+ },
+ },
+}
+
+
+def builtin_document_loader(url: str, options={}):
+ # Get URL without scheme
+ pieces = urllib_parse.urlparse(url)
+ if pieces.hostname is None:
+ raise JsonLdError(
+ f"No schema built-in for {url!r}",
+ "jsonld.LoadDocumentError",
+ code="loading document failed",
+ cause="NoHostnameError",
+ )
+ key = pieces.hostname + pieces.path.rstrip("/")
+ try:
+ return schemas[key]
+ except KeyError:
+ raise JsonLdError(
+ f"No schema built-in for {key!r}",
+ "jsonld.LoadDocumentError",
+ code="loading document failed",
+ cause="KeyError",
+ )
+
+
+class LDDocument:
+ """
+ Utility class for dealing with a document a bit more easily
+ """
+
+ def __init__(self, json_data):
+ self.items = {}
+ for entry in jsonld.flatten(jsonld.expand(json_data)):
+ item = LDItem(self, entry)
+ self.items[item.id] = item
+
+ def by_type(self, type):
+ for item in self.items.values():
+ if item.type == type:
+ yield item
+
+
+class LDItem:
+ """
+ Represents a single item in an LDDocument
+ """
+
+ def __init__(self, document, data):
+ self.data = data
+ self.document = document
+ self.id = self.data["@id"]
+ if "@type" in self.data:
+ self.type = self.data["@type"][0]
+ else:
+ self.type = None
+
+ def get(self, key):
+ """
+ Gets the first value of the given key, or None if it's not present.
+ If it's an ID reference, returns the other Item if possible, or the raw
+ ID if it's not supplied.
+ """
+ contents = self.data.get(key)
+ if not contents:
+ return None
+ id = contents[0].get("@id")
+ value = contents[0].get("@value")
+ if value is not None:
+ return value
+ if id in self.document.items:
+ return self.document.items[id]
+ else:
+ return id