module ap.activity_stream; import std.algorithm; import std.array; import std.datetime; import std.format; import std.json; import util; union Store { string str; ASObject[] array; Link[] links; } enum ObjectType { Object, String, Array, } /++ * Basic ActivityStream Object * https://www.w3.org/TR/activitypub/#obj +/ class ASObject { // Required fields (for root objects) JSONValue context; /// Must be activitystream context string id; /// AP requirement for unique identifier string type; /// AP requirement for type of object // Optional fields ASObject attachment; ASObject attributedTo; ASObject audience; string content; string name; DateTime endTime; ASObject generator; ASObject icon; ASObject image; ASObject inReplyTo; ASObject location; ASObject preview; DateTime published; Collection replies; DateTime startTime; string summary; ASObject tag; DateTime updated; Link url; ASObject to; ASObject bto; ASObject cc; ASObject bcc; string mediaType; string duration; JSONValue raw; protected Store m_store; protected ObjectType m_objType; this() { } /++ + Constructs the object according to json source + Params: + json = the source json + Returns: Object +/ this(JSONValue json) { switch (json.type) { case JSONType.ARRAY: this.m_objType = ObjectType.Array; this.m_store.array = json.array.map!(item => new ASObject(item)).array; break; case JSONType.OBJECT: this.m_objType = ObjectType.Object; // String properties optional(json, "context", context); optional(json, "id", id); optional(json, "type", type); optional(json, "content", content); optional(json, "name", name); optional(json, "summary", summary); optional(json, "mediaType", mediaType); optional(json, "duration", duration); // AS Object properties generator = new ASObject(json.optional!JSONValue("generator")); icon = new ASObject(json.optional!JSONValue("icon")); image = new ASObject(json.optional!JSONValue("image")); inReplyTo = new ASObject(json.optional!JSONValue("inReplyTo")); location = new ASObject(json.optional!JSONValue("location")); preview = new ASObject(json.optional!JSONValue("preview")); tag = new ASObject(json.optional!JSONValue("tag")); to = new ASObject(json.optional!JSONValue("to")); bto = new ASObject(json.optional!JSONValue("bto")); cc = new ASObject(json.optional!JSONValue("cc")); bcc = new ASObject(json.optional!JSONValue("bcc")); const(JSONValue)* j; // Date properties // if ((j = "endTime" in json) != null) // endTime = DateTime.fromISOString(json["endTime"].str); // if ((j = "published" in json) != null) // published = DateTime.fromISOString(json["published"].str); // if ((j = "startTime" in json) != null) // startTime = DateTime.fromISOString(json["startTime"].str); // if ((j = "updated" in json) != null) // updated = DateTime.fromISOString(json["updated"].str); url = new Link(json.optional!JSONValue("url")); break; case JSONType.STRING: this.m_objType = ObjectType.String; this.m_store.str = json.str; break; case JSONType.NULL: break; default: throw new Exception("Unrecognized ActivityStream Object JSON format: " ~ json.type); } raw = json; } string stringRep(int indentation = 0, string indentStr = " ") const { string[] output; string indent = ""; string j; if (this.m_objType == ObjectType.String) return format("%s%s\n", indent, this.m_store.str); if (this.m_objType == ObjectType.Array) { output ~= "["; foreach (obj; this.m_store.array) output ~= format("%s%s%s%s,", indent, indentStr, indentStr, obj.stringRep(indentation + 1)); output ~= format("%s%s]", indent, indentStr); return output.length == 2 ? "" : output.join("\n"); } for (int i = 0; i < indentation; i++) indent ~= indentStr; output ~= format("%s {", this.type); if (id) output ~= format("%s%sid = %s", indent, indentStr, id); if (content) output ~= format("%s%scontent = %s", indent, indentStr, content); if (name) output ~= format("%s%sname = %s", indent, indentStr, name); // if (summary) // output ~= format("%s%ssummary = %s", indent, indentStr, summary); if (mediaType) output ~= format("%s%smediaType = %s", indent, indentStr, mediaType); if (duration) output ~= format("%s%sduration = %s", indent, indentStr, duration); if (url && (j = url.stringRep(indentation + 1)) != string.init) output ~= format("%s%surl = %s", indent, indentStr, j); if (icon && (j = icon.stringRep(indentation + 1)) != string.init) output ~= format("%s%sicon = %s", indent, indentStr, j); if (image && (j = image.stringRep(indentation + 1)) != string.init) output ~= format("%s%simage = %s", indent, indentStr, j); if (tag && (j = tag.stringRep(indentation + 1)) != string.init) output ~= format("%s%stag = %s", indent, indentStr, j); output ~= indent ~ "}"; return output.length == 2 ? "" : output.join("\n"); } } class Link { string type; string href; string[] rel; string mediaType; string name; int height, width; ASObject preview; protected Store m_store; protected ObjectType m_objType; JSONValue raw; this() { } this(JSONValue json) { switch (json.type) { case JSONType.ARRAY: m_objType = ObjectType.Array; m_store.links = json.array.map!(item => new Link(item)).array; break; case JSONType.OBJECT: m_objType = ObjectType.Object; optional(json, "type", type); optional(json, "href", href); optional(json, "mediaType", mediaType); optional(json, "name", name); optional(json, "height", height); optional(json, "width", width); const(JSONValue)* j; if ((j = "rel" in json) != null) rel = (*j).array.map!(item => item.str).array; preview = new ASObject(json.optional!JSONValue("preview")); break; case JSONType.STRING: m_objType = ObjectType.String; m_store.str = json.str; break; case JSONType.NULL: break; default: throw new Exception("Unrecognized ActivityStream Link JSON format"); } this.raw = json; } string stringRep(int indentation = 0, string indentStr = " ") const { string[] output; string indent = ""; string j; if (this.m_objType == ObjectType.String) return format("%s%s", indent, this.m_store.str); for (int i = 0; i < indentation; i++) indent ~= indentStr; output ~= format("%s {", this.type); if (href) output ~= format("%s%shref = %s", indent, indentStr, href); if (mediaType) output ~= format("%s%smediaType = %s", indent, indentStr, mediaType); if (name) output ~= format("%s%sname = %s", indent, indentStr, name); if (height) output ~= format("%s%sheight = %d", indent, indentStr, height); if (width) output ~= format("%s%swidth = %d", indent, indentStr, width); if (preview && (j = preview.stringRep(indentation + 1)) != string.init) output ~= format("%s%spreview = %s", indent, indentStr, j); output ~= indent ~ "}"; if (output.length == 2) return ""; return output.join("\n"); } } class Collection : ASObject { int totalItems; CollectionPage current; CollectionPage first; CollectionPage last; ASObject items; this(JSONValue json) { super(json); if (this.m_objType != ObjectType.Object) throw new Exception("Wrong Collection format?"); optional(json, "totalItems", totalItems); current = new CollectionPage(json.optional!JSONValue("current")); first = new CollectionPage(json.optional!JSONValue("first")); last = new CollectionPage(json.optional!JSONValue("last")); items = new ASObject(json.optional!JSONValue("items")); } } class CollectionPage : Collection { CollectionPage partOf; CollectionPage next; CollectionPage prev; this(JSONValue json) { super(json); if (this.m_objType != ObjectType.Object) throw new Exception("Wrong CollectionPage format?"); partOf = new CollectionPage(json.optional!JSONValue("partOf")); next = new CollectionPage(json.optional!JSONValue("next")); prev = new CollectionPage(json.optional!JSONValue("prev")); } }