diff --git a/source/ap/activity_stream.d b/source/ap/activity_stream.d new file mode 100644 index 0000000..e7895b5 --- /dev/null +++ b/source/ap/activity_stream.d @@ -0,0 +1,50 @@ +module ap.activity_stream; + +import std.json; + +import util; + +/++ + * Basic ActivityStream Object + * https://www.w3.org/TR/activitypub/#obj + +/ +class ASObject { + string context; /// Must be activitystream context + string id; /// AP requirement for unique identifier + string type; /// AP requirement for type of object + + JSONValue raw; + + /++ + + Constructs the object according to json source + + Params: + + json = the source json + + Returns: Object + +/ + this(JSONValue json) { + with (this) { + const(JSONValue)* ascontext = "@context" in json; + + if (ascontext == null) + throw new Exception("No context in ActivityStream Object"); + + switch ((*ascontext).type) { + case JSONType.ARRAY: + context = (*ascontext)[0].str; + break; + + case JSONType.STRING: + context = (*ascontext).str; + break; + + default: + throw new Exception("Invalid @context type for ActivityStream Object"); + } + + id = json.requiredKey!"id".str; + type = json.requiredKey!"type".str; + + raw = json; + } + } +} diff --git a/source/ap/actor.d b/source/ap/actor.d new file mode 100644 index 0000000..3dbef71 --- /dev/null +++ b/source/ap/actor.d @@ -0,0 +1,77 @@ +module ap.actor; + +import std.json; + +import ap.activity_stream; +import util; + +/++ + * Represents an ActivityPub Actor + * https://www.w3.org/TR/activitypub/#actors + +/ +class Actor : ASObject { + /* Required fields */ + string inbox; /// Link to messages received by this actor. Required + string outbox; /// Link to messages produced by this actor. Required + string following; /// Link to a collection of the actors that this actor is following. Required + string followers; /// Link to a collection of the actors that follow this actor. Required + + /* Optional fields */ + string liked; /// Link to a collection of objects this actor has liked + string streams; /// Supplementary list of Collections of interest + string preferredUsername; /// A short username for referencing this actor + ActorEndpoints endpoints; /// Useful endpoints for this actor + + JSONValue raw; + + /++ + + Creates an Actor object based on its json representation + + Params: + + json = ActivityPub actor JSON representation + +/ + this(JSONValue json) { + super(json); + + with (this) { + inbox = json.requiredKey!"inbox".str; + outbox = json.requiredKey!"outbox".str; + following = json.requiredKey!"following".str; + followers = json.requiredKey!"followers".str; + + liked = json.checkKey!string("liked"); + streams = json.checkKey!string("streams"); + preferredUsername = json.checkKey!string("preferredUsername"); + + if (JSONValue* j = "endpoints" in json) + endpoints = ActorEndpoints.fromJson(*j); + else + endpoints = ActorEndpoints(); + + raw = json; + } + } +} + +struct ActorEndpoints { + string proxyUrl; + string oauthAuthorizationEndpoint; + string oauthTokenEndpoint; + string provideClientKey; + string signClientKey; + string sharedInbox; + + static ActorEndpoints fromJson(JSONValue json) { + ActorEndpoints endpoints = ActorEndpoints(); + + with (endpoints) { + proxyUrl = json.checkKey!string("proxyUrl"); + oauthAuthorizationEndpoint = json.checkKey!string("oauthAuthorizationEndpoint"); + oauthTokenEndpoint = json.checkKey!string("oauthTokenEndpoint"); + provideClientKey = json.checkKey!string("provideClientKey"); + signClientKey = json.checkKey!string("signClientKey"); + sharedInbox = json.checkKey!string("sharedInbox"); + } + + return endpoints; + } +} diff --git a/source/ap/errors.d b/source/ap/errors.d new file mode 100644 index 0000000..cc63fd1 --- /dev/null +++ b/source/ap/errors.d @@ -0,0 +1,10 @@ +module ap.errors; + +/++ + * Base exception for ActivityPub + +/ +class APException : Exception { + this(string msg) { + super(msg); + } +} diff --git a/source/util.d b/source/util.d new file mode 100644 index 0000000..6612cd4 --- /dev/null +++ b/source/util.d @@ -0,0 +1,20 @@ +module util; + +import std.format; +import std.json; + +JSONValue requiredKey(string key)(JSONValue val) { + const(JSONValue)* j = key in val; + + if (j) + return *j; + + throw new Exception(format("Key %s not found in json", key)); +} + +T checkKey(T)(JSONValue val, string key) { + if (JSONValue* j = key in val) + return (*j).get!T; + + return T.init; +} diff --git a/source/webfinger.d b/source/webfinger.d index c917b3a..998bd14 100644 --- a/source/webfinger.d +++ b/source/webfinger.d @@ -22,7 +22,7 @@ PRequest buildRequest(string uri, string[string] params) { PRequest rq = PRequest(); rq.method = "GET"; - rq.url = format("http://%s/.well-known/webfinger", uri); + rq.url = format("https://%s/.well-known/webfinger", uri); rq.headers["Accept"] = AcceptedWebfingerContentType; QueryParam[] p; @@ -49,3 +49,12 @@ JSONValue requestAcct(string uri, string acct) { return parseJSON(rs.responseBody().toString()); } + +JSONValue requestAcct(string handle) { + string uri, acct; + + uri = handle.split("@")[$ - 1]; + acct = handle.split("@")[$ - 2]; + + return requestAcct(uri, acct); +}