private async Task AddCollection(ASObject entity, string obj, string type, string parent) { var collection = await _collection.NewCollection(EntityStore, null, type, parent); await EntityStore.StoreEntity(collection); entity.Replace(obj, ASTerm.MakeId(collection.Id)); }
public async Task <ASObject> BuildFakeObject(APEntity entity, string fragment) { if (!EntityData.IsActor(entity.Data)) { return(null); } if (fragment == "key") { var key = await _keyService.GetKey(entity.Id); var salm = new MagicKey(key.PrivateKey); var pemData = salm.AsPEM; var keyObj = new ASObject(); keyObj.Replace("owner", ASTerm.MakeId(entity.Id)); keyObj.Replace("publicKeyPem", ASTerm.MakePrimitive(pemData)); keyObj.Id = $"{entity.Id}#key"; return(keyObj); } else if (fragment == "endpoints") { var data = entity.Data; var idu = new Uri(entity.Id); var basePath = $"{idu.Scheme}://{idu.Host}{(idu.IsDefaultPort?"":$":{idu.Port}")}{_configuration.BasePath}"; var endpoints = new ASObject(); endpoints.Replace("oauthAuthorizationEndpoint", ASTerm.MakeId(basePath + "oauth/authorize?id=" + Uri.EscapeDataString(entity.Id))); endpoints.Replace("oauthTokenEndpoint", ASTerm.MakeId(basePath + "oauth/token?")); endpoints.Replace("settingsEndpoint", ASTerm.MakeId(basePath + "settings/auth")); endpoints.Replace("uploadMedia", data["outbox"].Single()); endpoints.Replace("relevantObjects", ASTerm.MakeId(basePath + "settings/relevant")); endpoints.Replace("proxyUrl", ASTerm.MakeId(basePath + "auth/proxy")); endpoints.Replace("jwks", ASTerm.MakeId(basePath + "auth/jwks?id=" + Uri.EscapeDataString(entity.Id))); endpoints.Replace("sharedInbox", ASTerm.MakeId(basePath + "auth/sharedInbox")); endpoints.Replace("search", ASTerm.MakeId(basePath + "auth/search")); endpoints.Id = entity.Id + "#endpoints"; return(endpoints); } return(null); }
public async Task <IActionResult> GetKey(string id) { var user = await _entityStore.GetEntity(id, false); if (user == null || !user.IsOwner) { return(NotFound()); } var key = await _context.GetKey(user.Id); var salm = new MagicKey(key.PrivateKey); var data = salm.AsPEM; var keyObj = new ASObject(); keyObj.Replace("owner", new ASTerm(user.Id)); keyObj.Replace("publicKeyPem", new ASTerm(data)); keyObj.Replace("id", new ASTerm($"{Request.Scheme}://{Request.Host}{Request.Path}{Request.QueryString}")); return(Content(keyObj.Serialize().ToString(), "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"")); }
private async Task <APEntity> AddCollection(ASObject entity, string obj, string parent, string store = null) { var collection = await _collection.NewCollection(EntityStore, null, "_" + obj, parent); var data = collection.Data; data.Replace("attributedTo", ASTerm.MakeId(parent)); collection.Data = data; await EntityStore.StoreEntity(collection); entity.Replace(store ?? obj, ASTerm.MakeId(collection.Id)); return(collection); }
private async Task <APEntity> _newCollection(string type, string attributedTo) { var obj = new ASObject(); obj["type"].Add(new ASTerm("OrderedCollection")); obj["attributedTo"].Add(new ASTerm(attributedTo)); obj.Replace("id", new ASTerm(await _entityData.FindUnusedID(_entityStore, obj, type, attributedTo))); var entity = APEntity.From(obj, true); entity.Type = "_" + type; entity = await _entityStore.StoreEntity(entity); await _entityStore.CommitChanges(); return(entity); }
public static APEntity From(string id, ASObject @object) { var type = (string)@object["type"].FirstOrDefault()?.Primitive; if (type?.StartsWith("_") != false) { type = "Unknown"; } @object.Replace("id", new ASTerm(id)); return(new APEntity { Id = id, Data = @object, Type = type }); }
private ASObject _getEndpoints(APEntity entity) { var data = entity.Data; var idu = new Uri(entity.Id); var basePath = $"{idu.Scheme}://{idu.Host}{_configuration.BasePath}"; var endpoints = new ASObject(); endpoints.Replace("oauthAuthorizationEndpoint", new ASTerm(basePath + "auth/oauth?id=" + Uri.EscapeDataString(entity.Id))); endpoints.Replace("oauthTokenEndpoint", new ASTerm(basePath + "auth/token?")); endpoints.Replace("settingsEndpoint", new ASTerm(basePath + "settings/auth")); endpoints.Replace("uploadMedia", new ASTerm((string)data["outbox"].Single().Primitive)); endpoints.Replace("relevantObjects", new ASTerm(basePath + "settings/relevant")); endpoints.Replace("proxyUrl", new ASTerm(basePath + "auth/proxy")); endpoints.Replace("jwks", new ASTerm(basePath + "auth/jwks?id=" + Uri.EscapeDataString(entity.Id))); endpoints.Replace("id", new ASTerm((string)null)); data.Replace("endpoints", new ASTerm(endpoints)); data.Replace("publicKey", new ASTerm(basePath + "auth/key?id=" + Uri.EscapeDataString(entity.Id))); return(data); }
public override async Task <bool> Handle() { if (MainObject.Type == "Follow") { if (Actor.Data["_:locked"].Any(a => !(bool)a.Primitive) || Actor.Data["locked"].Any(a => !(bool)a.Primitive)) { var accept = new ASObject(); accept.Replace("type", new ASTerm("Accept")); accept.Replace("actor", new ASTerm(Actor.Id)); accept.Replace("object", new ASTerm(MainObject.Id)); var claims = new ClaimsPrincipal(); var handler = ActivatorUtilities.CreateInstance <GetEntityMiddleware.GetEntityHandler>(_serviceProvider, claims); var outbox = await EntityStore.GetEntity((string)Actor.Data["outbox"].First().Primitive, false); await handler.ClientToServer(outbox, accept); } return(true); } if (MainObject.Type != "Like" && MainObject.Type != "Announce") { return(true); } var toFollowOrLike = await EntityStore.GetEntity((string)MainObject.Data["object"].Single().Primitive, false); if (toFollowOrLike == null || !toFollowOrLike.IsOwner) { return(true); // not going to update side effects now. } // sent to not the owner, so not updating! if ((string)toFollowOrLike.Data["attributedTo"].Single().Primitive != Actor.Id) { return(true); } string collectionId = null, objectToAdd = null; switch (MainObject.Type) { case "Like": collectionId = (string)toFollowOrLike.Data["likes"].SingleOrDefault()?.Primitive; objectToAdd = MainObject.Id; break; case "Announce": collectionId = (string)toFollowOrLike.Data["shares"].SingleOrDefault()?.Primitive; objectToAdd = MainObject.Id; break; } if (collectionId == null) { return(true); // no way to store followers/likse } var collection = await EntityStore.GetEntity(collectionId, false); var entityToAdd = await EntityStore.GetEntity(objectToAdd, true); if (entityToAdd == null) { throw new InvalidOperationException("Can't like or announce a non-existant object!"); } await _collection.AddToCollection(collection, entityToAdd); return(true); }
public async Task <APEntity> GetEntity(string id, bool doRemote) { if (id == "https://www.w3.org/ns/activitystreams#Public") { var aso = new ASObject(); aso.Replace("type", new ASTerm("Collection")); aso.Replace("id", new ASTerm("https://www.w3.org/ns/activitystreams#Public")); var ent = APEntity.From(aso); return(ent); } APEntity entity = null; if (Next != null) { entity = await Next.GetEntity(id, doRemote); } if (entity?.Type == "_:LazyLoad" && !doRemote) { return(null); } if ((entity != null && entity.Type != "_:LazyLoad") || !doRemote) { return(entity); } var loadUrl = id; if (entity?.Type == "_:LazyLoad") { loadUrl = (string)entity.Data["href"].First().Primitive; } if (loadUrl.StartsWith("tag:")) { return(null); } var htc = new HttpClient(); htc.DefaultRequestHeaders.TryAddWithoutValidation("Accept", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\", application/activity+json, application/json, application/atom+xml, text/html"); if (_context != null) { var deliveryService = _serviceProvider.GetRequiredService <DeliveryService>(); var user = await Next.GetEntity(_context.User.FindFirstValue(JwtTokenSettings.ActorClaim), false); if (user != null) { var jwt = await deliveryService.BuildFederatedJWS(user, id); htc.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue("Bearer", jwt); } } var response = await htc.GetAsync(loadUrl); if (!response.IsSuccessStatusCode) { response = await htc.GetAsync(loadUrl + ".atom"); // hack! if (!response.IsSuccessStatusCode) { return(null); } } var converters = new List <IConverterFactory> { new AS2ConverterFactory(), new AtomConverterFactory(false) }; tryAgain: ASObject data = null; foreach (var converter in converters) { if (converter.CanParse && ConverterHelpers.GetBestMatch(converter.MimeTypes, response.Content.Headers.ContentType.ToString()) != null) { data = await converter.Build(_serviceProvider, null).Parse(await response.Content.ReadAsStreamAsync()); break; } } if (data == null) { if (response.Headers.Contains("Link")) { var split = string.Join(", ", response.Headers.GetValues("Link")).Split(new[] { ", " }, StringSplitOptions.RemoveEmptyEntries); foreach (var spl in split) { var args = spl.Split(';').Select(a => a.Trim()).ToArray(); if (args.Contains("rel=\"alternate\"") && args.Contains("type=\"application/atom+xml\"")) { response = await htc.GetAsync(args[0].Substring(1, args[0].Length - 2)); goto tryAgain; } } } try { var links = (await response.Content.ReadAsStringAsync()).Split('\n'); var alt = links.FirstOrDefault(a => a.Contains("application/atom+xml") && a.Contains("alternate")); if (alt == null) { return(null); } var l = XDocument.Parse(alt + "</link>").Root; if (l.Attribute(XNamespace.None + "type")?.Value == "application/atom+xml") { response = await htc.GetAsync(l.Attribute(XNamespace.None + "href")?.Value); goto tryAgain; } } catch (Exception) { } return(null); } // forces out the old lazy load, if used await _entityFlattener.FlattenAndStore(Next, data, false); await Next.CommitChanges(); return(await Next.GetEntity(id, true)); }
public override async Task <bool> Handle() { if (MainObject.Type == "https://www.w3.org/ns/activitystreams#Follow") { if (Actor.Data["manuallyApprovesFollowers"].Any(a => !(bool)a.Primitive)) { var accept = new ASObject(); accept.Type.Add("https://www.w3.org/ns/activitystreams#Accept"); accept.Replace("actor", ASTerm.MakeId(Actor.Id)); accept.Replace("object", ASTerm.MakeId(MainObject.Id)); accept["to"].AddRange(MainObject.Data["actor"]); var claims = new ClaimsPrincipal(); var id = new ClaimsIdentity(); id.AddClaim(new Claim("actor", Actor.Id)); claims.AddIdentity(id); var handler = ActivatorUtilities.CreateInstance <GetEntityHandler>(_serviceProvider, claims, EntityStore); var outbox = await EntityStore.GetEntity(Actor.Data["outbox"].First().Id, false); await handler.ClientToServer(outbox, accept); } return(true); } if (MainObject.Type != "https://www.w3.org/ns/activitystreams#Like" && MainObject.Type != "https://www.w3.org/ns/activitystreams#Announce") { return(true); } var toFollowOrLike = await EntityStore.GetEntity(MainObject.Data["object"].Single().Id, false); if (toFollowOrLike == null) { await GetEntityTask.Make(MainObject.Data["object"].Single().Id, _connection); return(true); } // sent to not the owner, so not updating! if (toFollowOrLike.Data["attributedTo"].Single().Id != Actor.Id) { return(true); } string collectionId = null, objectToAdd = null; switch (MainObject.Type) { case "https://www.w3.org/ns/activitystreams#Like": collectionId = toFollowOrLike.Data["likes"].SingleOrDefault()?.Id; objectToAdd = MainObject.Id; break; case "https://www.w3.org/ns/activitystreams#Announce": collectionId = toFollowOrLike.Data["shares"].SingleOrDefault()?.Id; objectToAdd = MainObject.Id; break; } if (collectionId == null) { return(true); // no way to store followers/likse } var collection = await EntityStore.GetEntity(collectionId, false); var entityToAdd = await EntityStore.GetEntity(objectToAdd, true); if (entityToAdd == null) { throw new InvalidOperationException("Can't like or announce a non-existant object!"); } await _collection.AddToCollection(collection, entityToAdd); return(true); }
private ASObject _parseAuthor(XElement element) { var ao = new ASObject(); ao.Type.Add("https://www.w3.org/ns/activitystreams#Person"); // set preferredUsername and name { var atomName = element.Element(Atom + "name")?.Value; var pocoDisplayName = element.Element(PortableContacts + "displayName")?.Value; var pocoPreferredUsername = element.Element(PortableContacts + "preferredUsername")?.Value; ao.Replace("preferredUsername", ASTerm.MakePrimitive(pocoPreferredUsername ?? atomName)); ao.Replace("name", ASTerm.MakePrimitive(pocoDisplayName ?? pocoPreferredUsername ?? atomName)); } // set summary { var atomSummary = element.Element(Atom + "summary")?.Value; var pocoNote = element.Element(PortableContacts + "note")?.Value; ao.Replace("summary", ASTerm.MakePrimitive(pocoNote ?? atomSummary)); } string retrievalUrl = null; { foreach (var link in element.Elements(Atom + "link")) { var rel = link.Attribute(NoNamespace + "rel")?.Value; var type = link.Attribute(NoNamespace + "type")?.Value; var href = link.Attribute(NoNamespace + "href")?.Value; switch (rel) { case "avatar": var avatarObject = new ASObject(); avatarObject.Type.Add("https://www.w3.org/ns/activitystreams#Image"); avatarObject.Replace("mediaType", ASTerm.MakePrimitive(type)); var width = link.Attribute(AtomMedia + "width")?.Value; var height = link.Attribute(AtomMedia + "height")?.Value; if (width != null && height != null) { avatarObject.Replace("width", ASTerm.MakePrimitive(int.Parse(width))); avatarObject.Replace("height", ASTerm.MakePrimitive(int.Parse(height))); } avatarObject.Replace("url", ASTerm.MakePrimitive(href)); ao["icon"].Add(ASTerm.MakeSubObject(avatarObject)); break; case "alternate": if (type == "text/html") { if (retrievalUrl == null) { retrievalUrl = href; } ao["atomUri"].Add(ASTerm.MakePrimitive(href)); } break; case "self": if (type == "application/atom+xml") { retrievalUrl = href; } break; } } } // should be Mastodon *and* GNU social compatible: Mastodon uses uri as id if (element.Element(Atom + "id") != null) { ao.Id = element.Element(Atom + "id")?.Value; } else { ao.Id = element.Element(Atom + "uri")?.Value; } if (element.Element(Atom + "uri") != null) { ao["url"].Add(ASTerm.MakePrimitive(element.Element(Atom + "uri")?.Value)); } if (element.Element(Atom + "email") != null) { ao["email"].Add(ASTerm.MakePrimitive(element.Element(Atom + "email")?.Value)); } foreach (var url in element.Elements(PortableContacts + "urls")) { ao["url"].Add(ASTerm.MakePrimitive(url.Element(PortableContacts + "value")?.Value)); } if (retrievalUrl != null) { ao.Replace("atomUri", ASTerm.MakePrimitive(retrievalUrl)); } return(ao); }
private async Task <ASObject> _parseFeed(XElement element, string targetUser) { var ao = new ASObject(); ao.Type.Add("https://www.w3.org/ns/activitystreams#OrderedCollectionPage"); ao.Replace("_:origin", ASTerm.MakePrimitive("atom")); ao.Id = element.Element(Atom + "id").Value; if (element.Element(Atom + "title") != null) { ao.Replace("summary", ASTerm.MakePrimitive(element.Element(Atom + "title").Value)); } if (element.Element(Atom + "updated") != null) { ao.Replace("updated", ASTerm.MakePrimitive(element.Element(Atom + "updated").Value)); } var author = _parseAuthor(element.Element(Atom + "author")); ao.Replace("attributedTo", ASTerm.MakeSubObject(author)); var authorId = author.Id; foreach (var entry in element.Elements(Atom + "entry")) { ao["orderedItems"].Add(await _parseActivity(entry, authorId, targetUser)); } foreach (var link in element.Elements(Atom + "link")) { var rel = link.Attribute(NoNamespace + "rel").Value; var type = link.Attribute(NoNamespace + "type")?.Value; var href = link.Attribute(NoNamespace + "href").Value; if (rel == "alternate" && type == "text/html") { ao["url"].Add(ASTerm.MakePrimitive(href)); } else if (rel == "self" && type == "application/atom+xml") { author.Replace("atomUri", ASTerm.MakePrimitive(href)); } else { switch (rel) { case "salmon": ao["_:salmonUrl"].Add(ASTerm.MakePrimitive(href)); break; case "hub": ao["_:hubUrl"].Add(ASTerm.MakePrimitive(href)); break; case "prev": ao["prev"].Add(ASTerm.MakeId(href)); break; case "next": ao["next"].Add(ASTerm.MakeId(href)); break; } } } author["_:salmonUrl"].AddRange(ao["_:salmonUrl"]); author["_:hubUrl"].AddRange(ao["_:hubUrl"]); return(ao); }
private async Task <ASTerm> _parseActivity(XElement element, string authorId, string targetUser) { if (await _isSelf(element.Element(Atom + "id").Value)) { return(ASTerm.MakeId(await _fixActivityToObjectId(element.Element(Atom + "id").Value))); } var ao = new ASObject(); ao.Id = element.Element(Atom + "id").Value; ao.Replace("_:origin", ASTerm.MakePrimitive("atom")); var verb = _objectTypeToType(element.Element(ActivityStreams + "verb")?.Value) ?? "Post"; var originalVerb = verb; if (verb == "Unfollow" && (await _entityStore.GetEntity(element.Element(Atom + "id").Value, false))?.Type == "Follow") // egh egh egh, why, mastodon { ao.Id += "#unfollow"; } if (verb == "Unfavorite") { verb = "Undo"; } if (verb == "Unfollow") { verb = "Undo"; } if (verb == "Request-friend") { return(null); } if (verb == "Post") { verb = "Create"; } else if (verb == "Share") { verb = "Announce"; } else if (verb == "Favorite") { verb = "Like"; } #pragma warning disable 618 if (!_entityConfiguration.IsActivity(verb)) { return(null); } #pragma warning restore 618 ao.Type.Add("https://www.w3.org/ns/activitystreams#" + verb); if (element.Element(Atom + "title") != null) { ao.Replace("summary", ASTerm.MakePrimitive(element.Element(Atom + "title").Value)); } if (element.Element(Atom + "published") != null) { ao.Replace("published", ASTerm.MakePrimitive(element.Element(Atom + "published").Value)); } if (element.Element(Atom + "updated") != null) { ao.Replace("updated", ASTerm.MakePrimitive(element.Element(Atom + "updated").Value)); } if (element.Element(Atom + "author") != null) { var newAuthor = _parseAuthor(element.Element(Atom + "author")); authorId = newAuthor.Id; } if (authorId != null) { ao.Replace("actor", ASTerm.MakeId(authorId)); } string retrievalUrl = null; foreach (var link in element.Elements(Atom + "link")) { var rel = link.Attribute(NoNamespace + "rel").Value; var type = link.Attribute(NoNamespace + "type")?.Value; var href = link.Attribute(NoNamespace + "href").Value; if (rel == "self" && type == "application/atom+xml") { retrievalUrl = href; } else if (rel == "alternate" && type == "text/html") { ao["url"].Add(ASTerm.MakePrimitive(href)); if (retrievalUrl == null) { retrievalUrl = href; } } else if (rel == "mentioned") { if (href == "http://activityschema.org/collection/public") { href = "https://www.w3.org/ns/activitystreams#Public"; } ao["to"].Add(ASTerm.MakeId(href)); } } if (targetUser != null) { ao["cc"].Add(ASTerm.MakeId(targetUser)); } if (retrievalUrl != null) { ao.Replace("atomUri", ASTerm.MakePrimitive(retrievalUrl)); } if (element.Element(ActivityStreams + "object") != null) { var parsedActivityObject = await _parseActivityObject(element.Element(ActivityStreams + "object"), authorId, targetUser); if (verb == "Undo" && originalVerb == "Unfavorite") { parsedActivityObject = ASTerm.MakeId((await _relevantEntities.FindRelevantObject(authorId, "https://www.w3.org/ns/activitystreams#Like", _getId(parsedActivityObject))).FirstOrDefault()?.Id); } else if (verb == "Undo" && originalVerb == "Unfollow") { parsedActivityObject = ASTerm.MakeId((await _relevantEntities.FindRelevantObject(authorId, "https://www.w3.org/ns/activitystreams#Follow", _getId(parsedActivityObject))).FirstOrDefault()?.Id); } ao.Replace("object", parsedActivityObject); } else if (element.Element(ActivityStreams + "object-type") == null && originalVerb == "Unfollow") { // you thought Mastodon was bad? // GNU Social doesn't send an object in an unfollow. // .. what ao.Replace("object", ASTerm.MakeId((await _relevantEntities.FindRelevantObject(authorId, "https://www.w3.org/ns/activitystreams#Follow", targetUser)).FirstOrDefault()?.Id)); } else { ao.Replace("object", await _parseActivityObject(element, authorId, targetUser, true)); } return(ASTerm.MakeSubObject(ao)); }
private async Task <ASTerm> _parseActivityObject(XElement element, string authorId, string targetUser, bool isActivity = false) { if (!isActivity && element.Element(ActivityStreams + "verb") != null) { return(await _parseActivity(element, authorId, targetUser)); } var entity = await _entityStore.GetEntity(element.Element(Atom + "id")?.Value, true); if (entity != null) { if (entity.Type.Contains("https://www.w3.org/ns/activitystreams#Create")) { return(ASTerm.MakeId(entity.Data["object"].First().Id)); } return(ASTerm.MakeId(element.Element(Atom + "id")?.Value)); } var ao = new ASObject(); ao.Id = element.Element(Atom + "id")?.Value + (isActivity ? "#object" : ""); ao.Replace("kroeg:origin", ASTerm.MakePrimitive("atom")); var objectType = _objectTypeToType(element.Element(ActivityStreams + "object-type")?.Value); if (objectType == "https://www.w3.org/ns/activitystreams#Person") { return(ASTerm.MakeSubObject(_parseAuthor(element))); } ao.Type.Add(objectType); ao.Replace("attributedTo", ASTerm.MakeId(authorId)); if (element.Element(Atom + "summary") != null) { ao.Replace("summary", ASTerm.MakePrimitive(element.Element(Atom + "summary")?.Value)); } if (element.Element(Atom + "published") != null) { ao.Replace("published", ASTerm.MakePrimitive(element.Element(Atom + "published")?.Value)); } if (element.Element(Atom + "updated") != null) { ao.Replace("updated", ASTerm.MakePrimitive(element.Element(Atom + "updated")?.Value)); } ao.Replace("content", ASTerm.MakePrimitive(element.Element(Atom + "content")?.Value)); var mediaType = element.Element(Atom + "content")?.Attribute(NoNamespace + "type")?.Value; if (mediaType != null) { if (mediaType == "text") { mediaType = "text/plain"; } if (mediaType.Contains("/")) { ao.Replace("mediaType", ASTerm.MakePrimitive(mediaType)); } } if (element.Element(OStatus + "conversation") != null) { ao.Replace("conversation", ASTerm.MakePrimitive(element.Element(OStatus + "conversation").Attribute(NoNamespace + "ref")?.Value ?? element.Element(OStatus + "conversation").Value)); } if (element.Element(AtomThreading + "in-reply-to") != null) { var elm = element.Element(AtomThreading + "in-reply-to"); var @ref = await _findInReplyTo(elm.Attribute(NoNamespace + "ref").Value); var hrel = elm.Attribute(NoNamespace + "href")?.Value; if (hrel == null) { ao.Replace("inReplyTo", ASTerm.MakeId(@ref)); } else if (await _entityStore.GetEntity(@ref, false) != null) { ao.Replace("inReplyTo", ASTerm.MakeId(@ref)); } else { var lazyLoad = new ASObject(); lazyLoad.Id = @ref; lazyLoad.Type.Add("_:LazyLoad"); lazyLoad.Replace("href", ASTerm.MakePrimitive(hrel)); ao.Replace("inReplyTo", ASTerm.MakeSubObject(lazyLoad)); } } foreach (var tag in element.Elements(Atom + "category")) { var val = tag.Attribute(NoNamespace + "term").Value; var tagao = new ASObject(); tagao.Id = $"{_entityConfiguration.BaseUri}/tag/{val}"; tagao["name"].Add(ASTerm.MakePrimitive("#" + val)); tagao.Type.Add("Hashtag"); ao["tag"].Add(ASTerm.MakeSubObject(tagao)); } string retrievalUrl = null; foreach (var link in element.Elements(Atom + "link")) { var rel = link.Attribute(NoNamespace + "rel").Value; var type = link.Attribute(NoNamespace + "type")?.Value; var href = link.Attribute(NoNamespace + "href").Value; if (rel == "self" && type == "application/atom+xml") { retrievalUrl = href; } else if (rel == "alternate" && type == "text/html") { ao["url"].Add(ASTerm.MakePrimitive(href)); if (retrievalUrl == null) { retrievalUrl = href; } } else if (rel == "mentioned") { if (href == "http://activityschema.org/collection/public") { href = "https://www.w3.org/ns/activitystreams#Public"; } ao["to"].Add(ASTerm.MakeId(href)); } else if (rel == "enclosure") { // image var subAo = new ASObject(); subAo["url"].Add(ASTerm.MakePrimitive(href)); subAo["mediaType"].Add(ASTerm.MakePrimitive(type)); switch (type.Split('/')[0]) { case "image": subAo.Type.Add("https://www.w3.org/ns/activitystreams#Image"); break; case "video": subAo.Type.Add("https://www.w3.org/ns/activitystreams#Video"); break; default: continue; } if (link.Attribute(NoNamespace + "length") != null) { subAo["fileSize"].Add(ASTerm.MakePrimitive(int.Parse(link.Attribute(NoNamespace + "length").Value))); } ao["attachment"].Add(ASTerm.MakeSubObject(subAo)); } } return(ASTerm.MakeSubObject(ao)); }