diff --git a/DysonNetwork.Sphere/Activity/ActivityService.cs b/DysonNetwork.Sphere/Activity/ActivityService.cs
index df08abc..0fea3eb 100644
--- a/DysonNetwork.Sphere/Activity/ActivityService.cs
+++ b/DysonNetwork.Sphere/Activity/ActivityService.cs
@@ -1,4 +1,5 @@
using DysonNetwork.Sphere.Account;
+using DysonNetwork.Sphere.Connection.WebReader;
using DysonNetwork.Sphere.Discovery;
using DysonNetwork.Sphere.Post;
using DysonNetwork.Sphere.Publisher;
@@ -39,6 +40,31 @@ public class ActivityService(
}
}
+ if (debugInclude.Contains("articles") || Random.Shared.NextDouble() < 0.2)
+ {
+ var recentArticlesQuery = db.WebArticles
+ .Take(20); // Get a larger pool for randomization
+
+ // Apply random ordering 50% of the time
+ if (Random.Shared.NextDouble() < 0.5)
+ {
+ recentArticlesQuery = recentArticlesQuery.OrderBy(_ => EF.Functions.Random());
+ }
+ else
+ {
+ recentArticlesQuery = recentArticlesQuery.OrderByDescending(a => a.PublishedAt);
+ }
+
+ var recentArticles = await recentArticlesQuery.Take(5).ToListAsync();
+
+ if (recentArticles.Count > 0)
+ {
+ activities.Add(new DiscoveryActivity(
+ recentArticles.Select(x => new DiscoveryItem("article", x)).ToList()
+ ).ToActivity());
+ }
+ }
+
// Fetch a larger batch of recent posts to rank
var postsQuery = db.Posts
.Include(e => e.RepliedPost)
@@ -115,6 +141,27 @@ public class ActivityService(
).ToActivity());
}
}
+
+ if (debugInclude.Contains("articles") || Random.Shared.NextDouble() < 0.2)
+ {
+ var recentArticlesQuery = db.WebArticles
+ .Take(20); // Get a larger pool for randomization
+
+ // Apply random ordering 50% of the time
+ if (Random.Shared.NextDouble() < 0.5)
+ recentArticlesQuery = recentArticlesQuery.OrderBy(_ => EF.Functions.Random());
+ else
+ recentArticlesQuery = recentArticlesQuery.OrderByDescending(a => a.PublishedAt);
+
+ var recentArticles = await recentArticlesQuery.Take(5).ToListAsync();
+
+ if (recentArticles.Count > 0)
+ {
+ activities.Add(new DiscoveryActivity(
+ recentArticles.Select(x => new DiscoveryItem("article", x)).ToList()
+ ).ToActivity());
+ }
+ }
}
// Get publishers based on filter
diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebArticleController.cs b/DysonNetwork.Sphere/Connection/WebReader/WebArticleController.cs
new file mode 100644
index 0000000..ea7f8be
--- /dev/null
+++ b/DysonNetwork.Sphere/Connection/WebReader/WebArticleController.cs
@@ -0,0 +1,82 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace DysonNetwork.Sphere.Connection.WebReader;
+
+[ApiController]
+[Route("/feeds/articles")]
+public class WebArticleController(AppDatabase db) : ControllerBase
+{
+ ///
+ /// Get a list of recent web articles
+ ///
+ /// Maximum number of articles to return
+ /// Number of articles to skip
+ /// Optional feed ID to filter by
+ /// Optional publisher ID to filter by
+ /// List of web articles
+ [HttpGet]
+ public async Task GetArticles(
+ [FromQuery] int limit = 20,
+ [FromQuery] int offset = 0,
+ [FromQuery] Guid? feedId = null,
+ [FromQuery] Guid? publisherId = null
+ )
+ {
+ var query = db.WebArticles
+ .OrderByDescending(a => a.PublishedAt)
+ .Include(a => a.Feed)
+ .AsQueryable();
+
+ if (feedId.HasValue)
+ query = query.Where(a => a.FeedId == feedId.Value);
+ if (publisherId.HasValue)
+ query = query.Where(a => a.Feed.PublisherId == publisherId.Value);
+
+ var totalCount = await query.CountAsync();
+ var articles = await query
+ .Skip(offset)
+ .Take(limit)
+ .ToListAsync();
+
+ Response.Headers["X-Total"] = totalCount.ToString();
+
+ return Ok(articles);
+ }
+
+ ///
+ /// Get a specific web article by ID
+ ///
+ /// The article ID
+ /// The web article
+ [HttpGet("{id:guid}")]
+ [ProducesResponseType(404)]
+ public async Task GetArticle(Guid id)
+ {
+ var article = await db.WebArticles
+ .Include(a => a.Feed)
+ .FirstOrDefaultAsync(a => a.Id == id);
+
+ if (article == null)
+ return NotFound();
+
+ return Ok(article);
+ }
+
+ ///
+ /// Get random web articles
+ ///
+ /// Maximum number of articles to return
+ /// List of random web articles
+ [HttpGet("random")]
+ public async Task GetRandomArticles([FromQuery] int limit = 5)
+ {
+ var articles = await db.WebArticles
+ .OrderBy(_ => EF.Functions.Random())
+ .Include(a => a.Feed)
+ .Take(limit)
+ .ToListAsync();
+
+ return Ok(articles);
+ }
+}
\ No newline at end of file
diff --git a/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs b/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs
index 6547de8..17c540c 100644
--- a/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs
+++ b/DysonNetwork.Sphere/Connection/WebReader/WebFeedService.cs
@@ -112,7 +112,8 @@ public class WebFeedService(
{
var scrapedArticle = await webReaderService.ScrapeArticleAsync(itemUrl, cancellationToken);
preview = scrapedArticle.LinkEmbed;
- content = scrapedArticle.Content;
+ if (scrapedArticle.Content is not null)
+ content = scrapedArticle.Content;
}
else
{