Base mostly done, starting with UI polishing and features.

This commit is contained in:
Max 2023-06-11 02:28:16 +02:00
parent 18e4135130
commit 91b28de875
12 changed files with 142 additions and 106 deletions

View File

@ -77,7 +77,7 @@ namespace SharpRss
await using SqliteConnection dbc = new SqliteConnection(ConnectionString); await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open(); dbc.Open();
int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed
(url, (encoded_url,
title, title,
category_id, category_id,
feed_type, feed_type,
@ -90,7 +90,7 @@ namespace SharpRss
categories, categories,
image_url) image_url)
VALUES ( VALUES (
@Url, @EncodedUrl,
@Title, @Title,
@CategoryId, @CategoryId,
@FeedType, @FeedType,
@ -104,7 +104,7 @@ namespace SharpRss
@ImageUrl)", @ImageUrl)",
new new
{ {
Url = feed.OriginalUrl ?? string.Empty, EncodedUrl = feed.EncodedUrl,
Title = feed.Title ?? string.Empty, Title = feed.Title ?? string.Empty,
CategoryId = feed.CategoryId ?? string.Empty, CategoryId = feed.CategoryId ?? string.Empty,
FeedType = feed.FeedType ?? string.Empty, FeedType = feed.FeedType ?? string.Empty,
@ -118,7 +118,7 @@ namespace SharpRss
ImageUrl = feed.ImageUrl ?? string.Empty ImageUrl = feed.ImageUrl ?? string.Empty
}); });
if (affected == 0) if (affected == 0)
Log.Warning("Failed to add feed: {FeedUrl}", feed.OriginalUrl); Log.Warning("Failed to add feed: {FeedUrl}", feed.EncodedUrl);
} }
public static async Task<HashSet<FeedModel>> GetFeedsAsync(string[]? categoryIds = null) public static async Task<HashSet<FeedModel>> GetFeedsAsync(string[]? categoryIds = null)
{ {
@ -130,7 +130,7 @@ namespace SharpRss
{ {
FeedModel feedModel = new FeedModel() FeedModel feedModel = new FeedModel()
{ {
OriginalUrl = reader["url"].ToString(), EncodedUrl = reader["encoded_url"].ToString(),
Title = reader["title"].ToString(), Title = reader["title"].ToString(),
CategoryId = reader["category_id"].ToString(), CategoryId = reader["category_id"].ToString(),
FeedType = reader["feed_type"].ToString(), FeedType = reader["feed_type"].ToString(),
@ -143,10 +143,20 @@ namespace SharpRss
Categories = reader["categories"].ToString().Split(','), Categories = reader["categories"].ToString().Split(','),
ImageUrl = reader["image_url"].ToString() ImageUrl = reader["image_url"].ToString()
}; };
feedModel.ItemCount = await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed_item WHERE encoded_feed_url=@EncodedFeedUrl", new { EncodedFeedUrl = feedModel.EncodedUrl });
feeds.Add(feedModel); feeds.Add(feedModel);
} }
return feeds; return feeds;
} }
public static async Task<bool> DeleteFeedAsync(FeedModel feed, bool deleteItems = false)
{
if (feed == null) return false;
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
int affected = await dbc.ExecuteAsync("DELETE FROM feed WHERE encoded_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl });
if (affected > 0 && deleteItems)
await dbc.ExecuteAsync("DELETE FROM feed_item WHERE encoded_feed_url=@EncodedUrl", new { EncodedUrl = feed.EncodedUrl });
return affected > 0;
}
public static async Task SetFeedItemsAsync(HashSet<FeedItemModel> items) public static async Task SetFeedItemsAsync(HashSet<FeedItemModel> items)
{ {
@ -156,13 +166,13 @@ namespace SharpRss
await using SqliteTransaction dbTransaction = dbc.BeginTransaction(); await using SqliteTransaction dbTransaction = dbc.BeginTransaction();
foreach (FeedItemModel item in items) foreach (FeedItemModel item in items)
{ {
int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed_item (id, feed_url, read, title, description, link, last_updated, publishing_date, authors, categories, content) int affected = await dbc.ExecuteAsync(@"INSERT OR REPLACE INTO feed_item (id, encoded_feed_url, read, title, description, link, last_updated, publishing_date, authors, categories, content)
VALUES (@Id, @FeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)", VALUES (@Id, @EncodedFeedUrl, @Read, @Title, @Description, @Link, @LastUpdated, @PublishingDate, @Authors, @Categories, @Content)",
transaction: dbTransaction, transaction: dbTransaction,
param: new param: new
{ {
Id = item.Id ?? string.Empty, Id = item.Id ?? string.Empty,
FeedUrl = item.FeedUrl ?? string.Empty, EncodedFeedUrl = item.EncodedFeedUrl ?? string.Empty,
Read = item.Read.ToString(), Read = item.Read.ToString(),
Title = item.Title ?? string.Empty, Title = item.Title ?? string.Empty,
Description = item.Description ?? string.Empty, Description = item.Description ?? string.Empty,
@ -177,26 +187,19 @@ namespace SharpRss
} }
dbTransaction.Commit(); dbTransaction.Commit();
} }
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? encodedFeedUrls)
private static string FormatParametersFromArray(string[] dbParams)
{
string[] formatted = dbParams.Select(s => $"'{s}'").ToArray();
return string.Join(", ", formatted);
}
public static async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string[]? feedUrls)
{ {
await using SqliteConnection dbc = new SqliteConnection(ConnectionString); await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
dbc.Open(); dbc.Open();
HashSet<FeedItemModel> items = new HashSet<FeedItemModel>(); HashSet<FeedItemModel> items = new HashSet<FeedItemModel>();
await using DbDataReader reader = await dbc.ExecuteReaderAsync(feedUrls == null ? "SELECT * FROM feed_item" : $"SELECT * FROM feed_item WHERE feed_url IN({FormatParametersFromArray(feedUrls)})"); await using DbDataReader reader = await dbc.ExecuteReaderAsync(encodedFeedUrls == null ? "SELECT * FROM feed_item" : $"SELECT * FROM feed_item WHERE encoded_feed_url IN({FormatParametersFromArray(encodedFeedUrls)})");
//NOTE(dbg): While debugging this part of the function the code below (Reader.ReadAsync) will return 0, because the debugger already reads the items from the reader. //NOTE(dbg): While debugging this part of the function the code below (Reader.ReadAsync) will return 0, because the debugger already reads the items from the reader.
while (await reader.ReadAsync()) while (await reader.ReadAsync())
{ {
FeedItemModel feedItemModel = new FeedItemModel() FeedItemModel feedItemModel = new FeedItemModel()
{ {
Id = reader["id"].ToString(), Id = reader["id"].ToString(),
FeedUrl = reader["feed_url"].ToString(), EncodedFeedUrl = reader["encoded_feed_url"].ToString(),
Read = bool.Parse(reader["read"].ToString()), Read = bool.Parse(reader["read"].ToString()),
Title = reader["title"].ToString(), Title = reader["title"].ToString(),
Description = reader["description"].ToString(), Description = reader["description"].ToString(),
@ -212,7 +215,6 @@ namespace SharpRss
Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count); Log.Debug("Fetching feed items resulted: {ItemCount} item(s)", items.Count);
return items; return items;
} }
public static async void Initialize() public static async void Initialize()
{ {
if (_isInitialized) return; if (_isInitialized) return;
@ -223,12 +225,23 @@ namespace SharpRss
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS category (id STRING PRIMARY KEY, name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING)"); await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS category (id STRING PRIMARY KEY, name STRING NOT NULL, hex_color STRING NOT NULL, icon STRING)");
Log.Verbose("Checking table: {Table}", "feed"); Log.Verbose("Checking table: {Table}", "feed");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed (url STRING PRIMARY KEY, title STRING, category_id STRING, feed_type STRING, feed_version STRING, description STRING, language STRING, copyright STRING, publication_date INT, last_updated INT, categories STRING, image_url STRING)"); await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed (encoded_url STRING PRIMARY KEY, title STRING, category_id STRING, feed_type STRING, feed_version STRING, description STRING, language STRING, copyright STRING, publication_date INT, last_updated INT, categories STRING, image_url STRING)");
Log.Verbose("Checking table: {Table}", "feed_item"); Log.Verbose("Checking table: {Table}", "feed_item");
await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, feed_url STRING, read STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)"); await dbc.ExecuteAsync("CREATE TABLE IF NOT EXISTS feed_item (id STRING PRIMARY KEY, encoded_feed_url STRING, read STRING, title STRING, description STRING, link STRING, last_updated INT, publishing_date INT, authors STRING, categories STRING, content STRING)");
Log.Verbose("Checking database done!"); Log.Verbose("Checking database done!");
_isInitialized = true; _isInitialized = true;
} }
private static string FormatParametersFromArray(string[] dbParams)
{
string[] formatted = dbParams.Select(s => $"'{s}'").ToArray();
return string.Join(", ", formatted);
}
public static async Task<int> GetFeedCountAsync(string[] encodedFeedUrls)
{
await using SqliteConnection dbc = new SqliteConnection(ConnectionString);
return await dbc.ExecuteScalarAsync<int>("SELECT COUNT(*) FROM feed WHERE encoded_url IN(@Urls)");
}
} }
} }

View File

@ -147,7 +147,7 @@ namespace SharpRss
{ {
Parameters = Parameters =
{ {
new SqliteParameter("url", feedModel.OriginalUrl ?? string.Empty), new SqliteParameter("url", feedModel.EncodedUrl ?? string.Empty),
new SqliteParameter("title", feedModel.Title ?? string.Empty), new SqliteParameter("title", feedModel.Title ?? string.Empty),
new SqliteParameter("groupId", feedModel.CategoryId ?? string.Empty), new SqliteParameter("groupId", feedModel.CategoryId ?? string.Empty),
new SqliteParameter("feedType", feedModel.FeedType ?? string.Empty), new SqliteParameter("feedType", feedModel.FeedType ?? string.Empty),
@ -242,7 +242,7 @@ namespace SharpRss
{ {
Parameters = Parameters =
{ {
new SqliteParameter("Url", feedModel.OriginalUrl) new SqliteParameter("Url", feedModel.EncodedUrl)
} }
}; };
int affected = await cmd.ExecuteNonQueryAsync(); int affected = await cmd.ExecuteNonQueryAsync();
@ -268,7 +268,7 @@ namespace SharpRss
FeedItemModel feedItemModel = new FeedItemModel() FeedItemModel feedItemModel = new FeedItemModel()
{ {
Id = reader["id"].ToString(), Id = reader["id"].ToString(),
FeedUrl = reader["feed_url"].ToString(), EncodedFeedUrl = reader["feed_url"].ToString(),
Read = int.TryParse(reader["read"].ToString(), out int parsedValue) && parsedValue != 0, Read = int.TryParse(reader["read"].ToString(), out int parsedValue) && parsedValue != 0,
Title = reader["title"].ToString(), Title = reader["title"].ToString(),
Description = reader["description"].ToString(), Description = reader["description"].ToString(),
@ -301,7 +301,7 @@ namespace SharpRss
{ {
cmd.Parameters.Clear(); cmd.Parameters.Clear();
cmd.Parameters.Add(new SqliteParameter("id", item.Id ?? string.Empty)); cmd.Parameters.Add(new SqliteParameter("id", item.Id ?? string.Empty));
cmd.Parameters.Add(new SqliteParameter("feedUrl", item.FeedUrl ?? string.Empty)); cmd.Parameters.Add(new SqliteParameter("feedUrl", item.EncodedFeedUrl ?? string.Empty));
cmd.Parameters.Add(new SqliteParameter("read", item.Read ? 1 : 0)); cmd.Parameters.Add(new SqliteParameter("read", item.Read ? 1 : 0));
cmd.Parameters.Add(new SqliteParameter("type", item.Type ?? string.Empty)); cmd.Parameters.Add(new SqliteParameter("type", item.Type ?? string.Empty));
cmd.Parameters.Add(new SqliteParameter("title", item.Title ?? string.Empty)); cmd.Parameters.Add(new SqliteParameter("title", item.Title ?? string.Empty));
@ -364,7 +364,7 @@ namespace SharpRss
{ {
FeedModel fetchedFeed = new FeedModel() FeedModel fetchedFeed = new FeedModel()
{ {
OriginalUrl = reader["url"].ToString(), EncodedUrl = reader["url"].ToString(),
Title = reader["title"].ToString(), Title = reader["title"].ToString(),
CategoryId = reader["group_id"].ToString(), CategoryId = reader["group_id"].ToString(),
FeedType = reader["feed_type"].ToString(), FeedType = reader["feed_type"].ToString(),

View File

@ -5,7 +5,7 @@ namespace SharpRss.Models
public class FeedItemModel public class FeedItemModel
{ {
public string? Id { get; set; } = string.Empty; public string? Id { get; set; } = string.Empty;
public string FeedUrl { get; set; } = string.Empty; public string EncodedFeedUrl { get; set; } = string.Empty;
public bool Read { get; set; } public bool Read { get; set; }
public string? Type { get; set; } = string.Empty; public string? Type { get; set; } = string.Empty;
public string? Title { get; set; } = string.Empty; public string? Title { get; set; } = string.Empty;

View File

@ -6,7 +6,7 @@ namespace SharpRss.Models
{ {
public class FeedModel public class FeedModel
{ {
public string OriginalUrl { get; set; } = string.Empty; public string EncodedUrl { get; set; } = string.Empty;
public string? Title { get; set; } = string.Empty; public string? Title { get; set; } = string.Empty;
public string? CategoryId { get; set; } = string.Empty; public string? CategoryId { get; set; } = string.Empty;
public string? FeedType { get; set; } = string.Empty; public string? FeedType { get; set; } = string.Empty;
@ -17,13 +17,14 @@ namespace SharpRss.Models
public DateTimeOffset? PublicationDate { get; set; } public DateTimeOffset? PublicationDate { get; set; }
public DateTimeOffset? LastUpdated { get; set; } = DateTimeOffset.Now; public DateTimeOffset? LastUpdated { get; set; } = DateTimeOffset.Now;
public string[]? Categories { get; set; } public string[]? Categories { get; set; }
public int ItemCount { get; set; }
private string _imageUrl = string.Empty; private string _imageUrl = string.Empty;
public string ImageUrl public string ImageUrl
{ {
get get
{ {
if (_imageUrl.IsNullEmptyWhiteSpace()) if (_imageUrl.IsNullEmptyWhiteSpace())
_imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(OriginalUrl).Host); _imageUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedUrl)).Host);
return _imageUrl; return _imageUrl;
} }
set set

View File

@ -9,36 +9,62 @@ using SharpRss.Models;
namespace SharpRss.Services namespace SharpRss.Services
{ {
/// <summary> /// <summary>
/// Managing RSS feeds and groups. /// Managing RSS feeds and categories.
/// </summary> /// </summary>
public class RssService public class RssService
{ {
public RssService() public RssService()
{ {
SetupTestGroupsAndFeedsAsync(); SetupTestCategoriesAndFeedsAsync();
} }
public async Task<HashSet<object>> GetCategoriesFeedsAsync() public async Task<HashSet<object>> GetCategoriesAndFeedsAsync()
{ {
HashSet<object> items = new HashSet<object>(); HashSet<object> items = new HashSet<object>();
items.UnionWith(await GetCategoriesAsync()); items.UnionWith(await GetCategoriesAsync());
items.UnionWith(await GetUngroupedFeedsAsync()); items.UnionWith(await GetUngroupedFeedsAsync());
return items; return items;
} }
public async Task<bool> CreateGroupAsync(CategoryModel group) => await DbAccess_Old.SetCategoryAsync(group);
public async Task<HashSet<CategoryModel>> GetCategoriesAsync() => await DbAccess.GetCategoriesAsync();
//TODO: Rework this! public async Task<CategoryModel?> CreateCategoryAsync(CategoryModel category) =>
// Subscribe to a feed. await DbAccess.SetCategoryAsync(category);
public async Task<HashSet<CategoryModel>> GetCategoriesAsync() => await DbAccess.GetCategoriesAsync();
public async Task<bool> AddSubscriptionAsync(string url, CategoryModel? category = null) public async Task<bool> AddSubscriptionAsync(string url, CategoryModel? category = null)
{ {
var syndication = SyndicationManager.CreateSyndication(url); var syndication = SyndicationManager.CreateSyndication(url);
if (!syndication.Fetched)
{
Log.Debug("Failed to fetch syndication feed!");
return false;
}
if (category != null) if (category != null)
syndication.Category = category; syndication.Category = category;
if (!syndication.Fetched) return false; try
{
await DbAccess.SetSyndicationAsync(syndication); await DbAccess.SetSyndicationAsync(syndication);
}
catch (Exception e)
{
Log.Error(e,"Error adding feed: {FeedUrl} to database!", url);
}
return true; return true;
} }
public async Task UpdateFeeds()
{
Log.Verbose("Fetching feeds...");
var feeds = await GetFeedsAsync();
}
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetFeedsAsync(categoryId == null ? null : new[]{ categoryId });
public async Task<HashSet<FeedModel>> GetUngroupedFeedsAsync() => await DbAccess.GetFeedsAsync(new []{""});
public async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string feedId, string? groupId = null) => await GetFeedItemsFromFeedsAsync(new[] { feedId }, groupId);
public async Task<HashSet<FeedItemModel>> GetFeedItemsFromFeedsAsync(string[] feedIds, string? groupId = null)
{
var items = await DbAccess.GetFeedItemsAsync(feedIds);
return items;
}
private static FeedModel FromResource(ISyndicationResource resource) private static FeedModel FromResource(ISyndicationResource resource)
{ {
FeedModel model = new FeedModel(); FeedModel model = new FeedModel();
@ -50,7 +76,7 @@ namespace SharpRss.Services
model.Title = rssFeed.Channel.Title; model.Title = rssFeed.Channel.Title;
model.Description = rssFeed.Channel.Description; model.Description = rssFeed.Channel.Description;
model.Copyright = rssFeed.Channel.Copyright; model.Copyright = rssFeed.Channel.Copyright;
model.OriginalUrl = rssFeed.Channel.SelfLink.ToString(); model.EncodedUrl = rssFeed.Channel.SelfLink.ToString();
model.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty; model.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
model.Language = rssFeed.Channel.Language?.ToString(); model.Language = rssFeed.Channel.Language?.ToString();
break; break;
@ -63,23 +89,6 @@ namespace SharpRss.Services
} }
return model; return model;
} }
public async Task UpdateFeeds()
{
Log.Verbose("Fetching feeds...");
var feeds = await GetFeedsAsync();
}
public async Task<HashSet<FeedModel>> GetFeedsAsync(string? categoryId = null) => await DbAccess.GetFeedsAsync(categoryId == null ? null : new[]{ categoryId });
public async Task<HashSet<FeedModel>> GetUngroupedFeedsAsync() => await DbAccess.GetFeedsAsync(new []{""});
public async Task<HashSet<FeedItemModel>> GetFeedItemsAsync(string feedId, string? groupId = null) => await GetFeedItemsFromFeedsAsync(new[] { feedId }, groupId);
public async Task<HashSet<FeedItemModel>> GetFeedItemsFromFeedsAsync(string[] feedIds, string? groupId = null)
{
var items = await DbAccess.GetFeedItemsAsync(feedIds);
return items;
}
private GenericSyndicationFeed? CreateFeed(string url) private GenericSyndicationFeed? CreateFeed(string url)
{ {
Uri feedUri = new Uri(url); Uri feedUri = new Uri(url);
@ -92,44 +101,41 @@ namespace SharpRss.Services
Log.Verbose("Fetching feed: {FeedUrl}", feedUri.ToString()); Log.Verbose("Fetching feed: {FeedUrl}", feedUri.ToString());
return GenericSyndicationFeed.Create(new Uri(url)); return GenericSyndicationFeed.Create(new Uri(url));
} }
private async void SetupTestGroupsAndFeedsAsync() private async void SetupTestCategoriesAndFeedsAsync()
{ {
CategoryModel NewsCat = new CategoryModel() { Name = "News" }; /*CategoryModel? newsCategory = await CreateCategoryAsync(new CategoryModel() { Name = "News" });
await AddSubscriptionAsync("https://www.nu.nl/rss/Algemeen", NewsCat); if (newsCategory != null)
await AddSubscriptionAsync("https://www.nu.nl/rss/Economie", NewsCat);
await AddSubscriptionAsync("http://fedoramagazine.org/feed/");
await AddSubscriptionAsync("https://itsfoss.com/feed");
//TODO: Make multiple adding of feed to a transaction, now throws an exception.
/*var groupRes = await CreateGroupAsync(new GroupModel() { Name = "Test" });
groupRes = await CreateGroupAsync(new GroupModel() { Name = "News" });
groupRes = await CreateGroupAsync(new GroupModel() { Name = "Tech" });
groupRes = await CreateGroupAsync(new GroupModel() { Name = "Science" });*/
/*Log.Verbose("Fetching feeds...");
var groups = await GetGroupsAsync();
GroupModel testGroup = groups.Single(x => x.Name == "Test");
try
{ {
var res = await AddFeed("http://fedoramagazine.org/feed/", testGroup); await AddSubscriptionAsync("https://www.nu.nl/rss/Algemeen", newsCategory);
res = await AddFeed("https://www.nasa.gov/rss/dyn/breaking_news.rss", testGroup); await AddSubscriptionAsync("https://www.nasa.gov/rss/dyn/breaking_news.rss", newsCategory);
res = await AddFeed("https://journals.plos.org/plosone/feed/atom", testGroup); await AddSubscriptionAsync("http://news.google.com/?output=atom", newsCategory);
res = await AddFeed("https://itsfoss.com/feed", testGroup); await AddSubscriptionAsync("https://www.ad.nl/home/rss.xml", newsCategory);
res = await AddFeed("https://advisories.ncsc.nl/rss/advisories", testGroup);
} }
catch (Exception e)
{
Log.Error(e, "Error fetching feeds!");
throw;
}*/
/*var groups = await GetGroupsAsync();
CategoryModel testGroup = groups.Single(x => x.Name == "News");*/
/*await AddFeedsAsync(new[] CategoryModel? techCategory = await CreateCategoryAsync(new CategoryModel() { Name = "Tech" });
if (techCategory != null)
{ {
"https://www.nu.nl/rss/Algemeen", await AddSubscriptionAsync("https://itsfoss.com/feed", techCategory);
"https://www.nu.nl/rss/Economie", await AddSubscriptionAsync("http://fedoramagazine.org/feed/", techCategory);
"https://www.nu.nl/rss/Sport", await AddSubscriptionAsync("https://arstechnica.com/feed/", techCategory);
"http://news.google.com/?output=atom" await AddSubscriptionAsync("https://feeds.arstechnica.com/arstechnica/gadgets", techCategory);
}, testGroup);*/ }
CategoryModel? youtubeCategory = await CreateCategoryAsync(new CategoryModel() { Name = "YouTube" });
if (youtubeCategory != null)
{
await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UCXuqSBlHAE6Xw-yeJA0Tunw", youtubeCategory);
await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UC1Et9K-hHf-P_LzQkE_Q3Jw", youtubeCategory);
await AddSubscriptionAsync("https://www.youtube.com/feeds/videos.xml?channel_id=UCsXVk37bltHxD1rDPwtNM8Q", youtubeCategory);
}
await AddSubscriptionAsync("http://www.digitaleoverheid.nl/feed/");
await AddSubscriptionAsync("http://www.digitaleoverheid.nl/agenda/feed/");
await AddSubscriptionAsync("https://feeds.rijksoverheid.nl/nieuws.rss");
await AddSubscriptionAsync("https://nl.wikipedia.org/w/index.php?title=Speciaal:RecenteWijzigingen&feed=atom");
await AddSubscriptionAsync("https://feeds.aivd.nl/nieuws.rss");
await AddSubscriptionAsync("https://blogs.microsoft.com/feed");
await AddSubscriptionAsync("https://www.europarl.europa.eu/rss/doc/top-stories/nl.xml");*/
} }
} }
} }

View File

@ -2,6 +2,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Text;
using Argotic.Common; using Argotic.Common;
using Argotic.Extensions.Core; using Argotic.Extensions.Core;
using Argotic.Syndication; using Argotic.Syndication;
@ -33,7 +34,7 @@ namespace SharpRss
{ {
Log.Error(e,"Could not get feed: {FeedUrl}", feedUrl); Log.Error(e,"Could not get feed: {FeedUrl}", feedUrl);
} }
return ConstructSyndicationContainer(syndicationFeed); return ConstructSyndicationContainer(syndicationFeed, feedUrl);
} }
public static Stream FeedToStream(GenericSyndicationFeed syndicationFeed) public static Stream FeedToStream(GenericSyndicationFeed syndicationFeed)
@ -46,7 +47,7 @@ namespace SharpRss
return memStream; return memStream;
} }
private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed) private static SyndicationContainer ConstructSyndicationContainer(GenericSyndicationFeed? syndicationFeed, string feedUrl)
{ {
SyndicationContainer container = new SyndicationContainer(); SyndicationContainer container = new SyndicationContainer();
if (syndicationFeed == null) if (syndicationFeed == null)
@ -62,14 +63,14 @@ namespace SharpRss
case SyndicationContentFormat.Rss: case SyndicationContentFormat.Rss:
RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource; RssFeed rssFeed = (RssFeed)container.SyndicationFeed.Resource;
if (rssFeed.Channel == null) break; if (rssFeed.Channel == null) break;
container.FeedModel.OriginalUrl = rssFeed.Channel.SelfLink?.ToString() ?? string.Empty; container.FeedModel.EncodedUrl = EncodeUrl(feedUrl);
container.FeedModel.Title = rssFeed.Channel.Title ?? string.Empty; container.FeedModel.Title = rssFeed.Channel.Title ?? string.Empty;
container.FeedModel.FeedType = rssFeed.Format.ToString() ?? string.Empty; container.FeedModel.FeedType = rssFeed.Format.ToString() ?? string.Empty;
container.FeedModel.FeedVersion = rssFeed.Version?.ToString() ?? string.Empty; container.FeedModel.FeedVersion = rssFeed.Version?.ToString() ?? string.Empty;
container.FeedModel.Description = rssFeed.Channel.Description ?? string.Empty; container.FeedModel.Description = rssFeed.Channel.Description ?? string.Empty;
container.FeedModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty; container.FeedModel.Language = rssFeed.Channel.Language?.ToString() ?? string.Empty;
container.FeedModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty; container.FeedModel.Copyright = rssFeed.Channel.Copyright ?? string.Empty;
container.FeedModel.PublicationDate = rssFeed.Channel.LastBuildDate is not { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.LastBuildDate) : DateTimeOffset.MinValue; container.FeedModel.PublicationDate = rssFeed.Channel.PublicationDate is { Ticks: > 0 } ? new DateTimeOffset(rssFeed.Channel.PublicationDate) : DateTimeOffset.MinValue;
container.FeedModel.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty<string>(); container.FeedModel.Categories = rssFeed.Channel.Categories?.Select(x => x.Value).ToArray() ?? Array.Empty<string>();
container.FeedModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty; container.FeedModel.ImageUrl = rssFeed.Channel.Image?.Url.ToString() ?? string.Empty;
foreach (var rssItem in rssFeed.Channel.Items) foreach (var rssItem in rssFeed.Channel.Items)
@ -77,7 +78,7 @@ namespace SharpRss
FeedItemModel itemModel = new FeedItemModel() FeedItemModel itemModel = new FeedItemModel()
{ {
Id = rssItem.Link?.ToString() ?? string.Empty, Id = rssItem.Link?.ToString() ?? string.Empty,
FeedUrl = container.FeedModel.OriginalUrl ?? string.Empty, EncodedFeedUrl = container.FeedModel.EncodedUrl ?? string.Empty,
Type = container.FeedModel.FeedType ?? string.Empty, Type = container.FeedModel.FeedType ?? string.Empty,
Title = rssItem.Title ?? string.Empty, Title = rssItem.Title ?? string.Empty,
Description = rssItem.Description ?? string.Empty, Description = rssItem.Description ?? string.Empty,
@ -93,14 +94,14 @@ namespace SharpRss
break; break;
case SyndicationContentFormat.Atom: case SyndicationContentFormat.Atom:
AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource; AtomFeed atomFeed = (AtomFeed)container.SyndicationFeed.Resource;
container.FeedModel.OriginalUrl = atomFeed.Id?.Uri.ToString() ?? string.Empty; container.FeedModel.EncodedUrl = EncodeUrl(feedUrl);
container.FeedModel.Title = atomFeed.Title?.Content ?? string.Empty; container.FeedModel.Title = atomFeed.Title?.Content ?? string.Empty;
container.FeedModel.FeedType = atomFeed.Format.ToString() ?? string.Empty; container.FeedModel.FeedType = atomFeed.Format.ToString() ?? string.Empty;
container.FeedModel.FeedVersion = atomFeed.Version?.ToString() ?? string.Empty; container.FeedModel.FeedVersion = atomFeed.Version?.ToString() ?? string.Empty;
container.FeedModel.Description = atomFeed.Subtitle?.Content ?? string.Empty; container.FeedModel.Description = atomFeed.Subtitle?.Content ?? string.Empty;
container.FeedModel.Language = atomFeed.Language?.ToString() ?? string.Empty; container.FeedModel.Language = atomFeed.Language?.ToString() ?? string.Empty;
container.FeedModel.Copyright = atomFeed.Rights?.Content ?? string.Empty; container.FeedModel.Copyright = atomFeed.Rights?.Content ?? string.Empty;
container.FeedModel.PublicationDate = atomFeed.UpdatedOn != null ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue; container.FeedModel.PublicationDate = atomFeed.UpdatedOn is { Ticks: > 0 } ? new DateTimeOffset(atomFeed.UpdatedOn) : DateTimeOffset.MinValue;
container.FeedModel.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty<string>(); container.FeedModel.Categories = atomFeed.Categories?.Select(x => x.Label).ToArray() ?? Array.Empty<string>();
container.FeedModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty; container.FeedModel.ImageUrl = atomFeed.Icon?.Uri.ToString() ?? string.Empty;
foreach (var entry in atomFeed.Entries) foreach (var entry in atomFeed.Entries)
@ -108,7 +109,7 @@ namespace SharpRss
FeedItemModel itemModel = new FeedItemModel() FeedItemModel itemModel = new FeedItemModel()
{ {
Id = entry.Id?.Uri.ToString() ?? string.Empty, Id = entry.Id?.Uri.ToString() ?? string.Empty,
FeedUrl = container.FeedModel?.OriginalUrl ?? string.Empty, EncodedFeedUrl = container.FeedModel?.EncodedUrl ?? string.Empty,
Type = container.FeedModel?.FeedType ?? string.Empty, Type = container.FeedModel?.FeedType ?? string.Empty,
Title = entry.Title?.Content ?? string.Empty, Title = entry.Title?.Content ?? string.Empty,
Description = entry.Summary?.Content ?? string.Empty, Description = entry.Summary?.Content ?? string.Empty,
@ -129,7 +130,12 @@ namespace SharpRss
container.Fetched = true; container.Fetched = true;
return container; return container;
} }
public static string EncodeUrl(string url) => Convert.ToBase64String(Encoding.UTF8.GetBytes(url));
public static string DecodeUrl(string base64)
{
byte[] bytes = Convert.FromBase64String(base64);
return Encoding.Default.GetString(bytes);
}
public static void Init() public static void Init()
{ {
DbAccess.Initialize(); DbAccess.Initialize();

View File

@ -1,4 +1,5 @@
using System; using System;
using SharpRss;
using SharpRss.Models; using SharpRss.Models;
using ToolQit; using ToolQit;
@ -17,7 +18,7 @@ namespace WebSharpRSS.Models
get get
{ {
if (Link == null || _faviconUrl != null) return _faviconUrl; if (Link == null || _faviconUrl != null) return _faviconUrl;
_faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(Link).Host); _faviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(EncodedFeedUrl)).Host);
return _faviconUrl; return _faviconUrl;
} }
} }

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Configuration; using System.Configuration;
using System.Linq; using System.Linq;
using MudBlazor; using MudBlazor;
using SharpRss;
using SharpRss.Models; using SharpRss.Models;
using ToolQit; using ToolQit;
@ -22,8 +23,9 @@ namespace WebSharpRSS.Models
{ {
FeedModel = feedModel; FeedModel = feedModel;
Title = feedModel.Title ?? string.Empty; Title = feedModel.Title ?? string.Empty;
if (FeedModel.OriginalUrl == null) return; if (FeedModel.EncodedUrl == null) return;
FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(FeedModel.OriginalUrl).Host); FaviconUrl = string.Format(Caretaker.Settings["Paths"].GetString("FaviconResolveUrl"), new Uri(SyndicationManager.DecodeUrl(FeedModel.EncodedUrl)).Host);
TotalItems = FeedModel.ItemCount;
} }
public readonly CategoryModel? CategoryModel; public readonly CategoryModel? CategoryModel;
public readonly FeedModel? FeedModel; public readonly FeedModel? FeedModel;
@ -36,5 +38,6 @@ namespace WebSharpRSS.Models
public bool HasChildren { get; set; } public bool HasChildren { get; set; }
public bool Loading { get; set; } public bool Loading { get; set; }
public bool IsExpanded { get; set; } public bool IsExpanded { get; set; }
public int TotalItems { get; set; }
} }
} }

View File

@ -57,7 +57,7 @@
else if (Cid != null) else if (Cid != null)
{ {
var feeds = await _rssService.GetFeedsAsync(Cid == string.Empty ? null : Cid); var feeds = await _rssService.GetFeedsAsync(Cid == string.Empty ? null : Cid);
var feedIds = feeds.Select(x => x.OriginalUrl); var feedIds = feeds.Select(x => x.EncodedUrl);
var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedIds.ToArray()); var feedItems = await _rssService.GetFeedItemsFromFeedsAsync(feedIds.ToArray());
items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet(); items = feedItems.Select(x => FeedItemData.FromModel(x)).OrderBy(x => x.PublishingDate).Reverse().ToHashSet();
} }

View File

@ -0,0 +1,6 @@
{
"Paths": {
"FaviconResolveUrl": "http://www.google.com/s2/favicons?domain={0}",
"LogPath": "/home/max/GitHub/SharpRSS/WebSharpRSS/logs/log_.json"
}
}

View File

@ -51,7 +51,7 @@
if (_selectedItem == null) return; if (_selectedItem == null) return;
if (_selectedItem.FeedModel != null) if (_selectedItem.FeedModel != null)
{ {
_navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.OriginalUrl}"); _navigation.NavigateTo($"/list?fid={_selectedItem.FeedModel.EncodedUrl}");
} }
else if (_selectedItem.CategoryModel != null) else if (_selectedItem.CategoryModel != null)
{ {
@ -71,7 +71,7 @@
{ {
Log.Verbose("Loading guide data..."); Log.Verbose("Loading guide data...");
_guideItems.Add(new TreeItemData(new CategoryModel() { Name = "All", Icon = Icons.Material.Filled.Home, HexColor = Colors.Blue.Accent1, Id = string.Empty })); _guideItems.Add(new TreeItemData(new CategoryModel() { Name = "All", Icon = Icons.Material.Filled.Home, HexColor = Colors.Blue.Accent1, Id = string.Empty }));
HashSet<object> items = await _rssService.GetCategoriesFeedsAsync(); HashSet<object> items = await _rssService.GetCategoriesAndFeedsAsync();
_guideItems.UnionWith(ModelToTreeItem(items)); _guideItems.UnionWith(ModelToTreeItem(items));
StateHasChanged(); StateHasChanged();

Binary file not shown.