aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/Endpoints/EndpointBase.cs6
-rw-r--r--src/Endpoints/RadioSearchEndpoint.cs17
-rw-r--r--src/I2R.LightNews.csproj2
-rw-r--r--src/Models/RadioCategorySearchResult.cs58
-rw-r--r--src/Models/RadioSeries.cs18
-rw-r--r--src/Pages/Index.cshtml82
-rw-r--r--src/Pages/Index.cshtml.cs10
-rw-r--r--src/Pages/NrkRadio.cshtml12
-rw-r--r--src/Pages/NrkRadio.cshtml.cs13
-rw-r--r--src/Pages/Shared/_Layout.cshtml7
-rw-r--r--src/Program.cs10
-rw-r--r--src/RadioIndexDb.cs57
-rw-r--r--src/Services/NrkNewsService.cs (renamed from src/Services/GrabberService.cs)8
-rw-r--r--src/Services/NrkRadioService.cs31
-rw-r--r--src/Utilities/SqliteConnectionHelpers.cs14
-rw-r--r--src/wwwroot/radio.js4
16 files changed, 292 insertions, 57 deletions
diff --git a/src/Endpoints/EndpointBase.cs b/src/Endpoints/EndpointBase.cs
new file mode 100644
index 0000000..e1b1d3d
--- /dev/null
+++ b/src/Endpoints/EndpointBase.cs
@@ -0,0 +1,6 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace I2R.LightNews.Endpoints;
+
+public class EndpointBase : ControllerBase
+{ } \ No newline at end of file
diff --git a/src/Endpoints/RadioSearchEndpoint.cs b/src/Endpoints/RadioSearchEndpoint.cs
new file mode 100644
index 0000000..79f24ce
--- /dev/null
+++ b/src/Endpoints/RadioSearchEndpoint.cs
@@ -0,0 +1,17 @@
+using Microsoft.AspNetCore.Mvc;
+
+namespace I2R.LightNews.Endpoints;
+
+public class RadioSearchEndpoint : EndpointBase
+{
+ private readonly NrkRadioService _radio;
+
+ public RadioSearchEndpoint(NrkRadioService radio) {
+ _radio = radio;
+ }
+
+ [HttpGet("~/radio-search")]
+ public async Task HandleASync(string q) {
+
+ }
+} \ No newline at end of file
diff --git a/src/I2R.LightNews.csproj b/src/I2R.LightNews.csproj
index b8d7ed9..3170a74 100644
--- a/src/I2R.LightNews.csproj
+++ b/src/I2R.LightNews.csproj
@@ -9,6 +9,8 @@
<PackageReference Include="AngleSharp" Version="0.17.1" />
<PackageReference Include="IOL.Helpers" Version="3.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="7.0.0" />
+ <PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.0" />
+ <PackageReference Include="Dapper" Version="2.0.123" />
</ItemGroup>
<ItemGroup>
diff --git a/src/Models/RadioCategorySearchResult.cs b/src/Models/RadioCategorySearchResult.cs
new file mode 100644
index 0000000..7fd4c5d
--- /dev/null
+++ b/src/Models/RadioCategorySearchResult.cs
@@ -0,0 +1,58 @@
+using System.Text.Json.Serialization;
+
+namespace I2R.LightNews.Models;
+
+public class RadioCategorySearchResult
+{
+ [JsonPropertyName("_links")]
+ public LinksModel Links { get; set; }
+
+ public List<LetterModel> Letters { get; set; }
+ public string Title { get; set; }
+ public List<SeriesModel> Series { get; set; }
+ public long TotalCount { get; set; }
+
+ public class SeriesModel
+ {
+ [JsonPropertyName("_links")]
+ public LinksModel Links { get; set; }
+
+ public Guid Id { get; set; }
+ public string SeriesId { get; set; }
+ public string SeasonId { get; set; }
+ public string Title { get; set; }
+ public string Type { get; set; }
+ public string InitialCharacter { get; set; }
+ public List<ImageModel> Images { get; set; }
+
+ public class ImageModel
+ {
+ public string Uri { get; set; }
+ public int Width { get; set; }
+ }
+
+ public class LinksModel
+ {
+ public RadioCategorySearchResult.LinksModel.LinkModel CustomSeason { get; set; }
+ }
+ }
+
+ public class LetterModel
+ {
+ public string Letter { get; set; }
+ public int Count { get; set; }
+ public string Link { get; set; }
+ }
+
+ public class LinksModel
+ {
+ public LinkModel NextPage { get; set; }
+ public LinkModel LastPage { get; set; }
+
+
+ public class LinkModel
+ {
+ public string Href { get; set; }
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Models/RadioSeries.cs b/src/Models/RadioSeries.cs
new file mode 100644
index 0000000..6bd4efe
--- /dev/null
+++ b/src/Models/RadioSeries.cs
@@ -0,0 +1,18 @@
+namespace I2R.LightNews.Models;
+
+public class RadioSeries
+{
+ public string Name { get; set; }
+ public string Description { get; set; }
+ public string Type { get; set; }
+ public Uri CanonicalUri { get; set; }
+ public List<Episode> Episodes { get; set; }
+
+ public class Episode
+ {
+ public string Title { get; set; }
+ public string Subtitle { get; set; }
+ public Uri SourceUri { get; set; }
+ public Uri CanonicalUri { get; set; }
+ }
+} \ No newline at end of file
diff --git a/src/Pages/Index.cshtml b/src/Pages/Index.cshtml
index 0501e3e..076e4f6 100644
--- a/src/Pages/Index.cshtml
+++ b/src/Pages/Index.cshtml
@@ -26,62 +26,64 @@
</small>
</p>
</footer>
- @section scripts {
- <script>
+@section scripts {
+ <script>
const ignoreSessionStorageKey = "frontpage_ignores";
function hide_article(el) {
const linkEl = el.closest(".news-link");
const ignoreLink = new URL(linkEl.querySelector("a").href).searchParams.get("url");
- const currentIgnores = sessionStorage.getItem(ignoreSessionStorageKey);
+ const currentIgnores = localStorage.getItem(ignoreSessionStorageKey);
let newIgnores = [];
if (currentIgnores) newIgnores = JSON.parse(currentIgnores);
newIgnores.push(ignoreLink)
- sessionStorage.setItem(ignoreSessionStorageKey, JSON.stringify(newIgnores));
+ localStorage.setItem(ignoreSessionStorageKey, JSON.stringify(newIgnores));
linkEl.remove()
}
- const ignores = sessionStorage.getItem(ignoreSessionStorageKey)
+ const ignores = localStorage.getItem(ignoreSessionStorageKey)
if (ignores) {
for (const ignore of JSON.parse(ignores)) {
- console.log(ignore)
- document.querySelector("a[href*='"+ignore+"']").closest(".news-link").remove();
+ document.querySelector("a[href*='" + ignore + "']").closest(".news-link").remove();
}
}
</script>
- }
- } else if (Model.Article != default) {
- <div id="art-header" style="display: flex; justify-content: space-between">
- <div>
- <h1>@Model.Article.Title</h1>
- <p>@Model.Article.Subtitle</p>
- </div>
- </div>
+}} else if (Model.Article != default) {
+ <details>
+ <summary>Detaljer</summary>
+ <div style="flex-direction:column">
+ @foreach (var author in Model.Article.Authors) {
+ <small style="white-space: nowrap"><b>@author.Name</b>: @author.Title</small>
+ <br/>
+ }
+ </div>
+ <div style="flex-direction: column">
+ @if (Model.Article.PublishedAt != default) {
+ <small style="white-space: nowrap">Publisert: @Model.Article.PublishedAt.ToString("dd-MM-yyyy hh:mm:ss")</small>
+ }
+ @if (Model.Article.UpdatedAt != default) {
+ <br/>
+ <small style="white-space: nowrap">Oppdatert: @Model.Article.UpdatedAt.ToString("dd-MM-yyyy hh:mm:ss")</small>
+ }
+ <br/>
+ <small>
+ <a href="@Model.Article.Href" no-interception>Les på nrk.no</a>
+ </small>
+ </div>
+ </details>
+ <div id="art-header" style="display: flex; justify-content: space-between">
+ <div>
+ <h1>@Model.Article.Title</h1>
+ <p>@Model.Article.Subtitle</p>
+ </div>
+ </div>
- <article id="art-body">
- @Html.Raw(Model.Article.Content)
- </article>
+ <article id="art-body">
+ @Html.Raw(Model.Article.Content)
+ </article>
- <footer>
- <div style="flex-direction:column">
- @foreach (var author in Model.Article.Authors) {
- <small style="white-space: nowrap"><b>@author.Name</b>: @author.Title</small>
- <br/>
- }
- </div>
- <div style="flex-direction: column">
- @if (Model.Article.PublishedAt != default) {
- <small style="white-space: nowrap">Publisert: @Model.Article.PublishedAt.ToString("dd-MM-yyyy hh:mm:ss")</small>
- }
- @if (Model.Article.UpdatedAt != default) {
- <br/>
- <small style="white-space: nowrap">Oppdatert: @Model.Article.UpdatedAt.ToString("dd-MM-yyyy hh:mm:ss")</small>
- }
- <br/>
- <small>
- <a href="@Model.Article.Href" no-interception>Les på nrk.no</a>
- </small>
- </div>
- </footer>
+ <footer>
+
+ </footer>
@section scripts {
<script>
@@ -91,4 +93,4 @@
});
})
</script>
-}} \ No newline at end of file
+} } \ No newline at end of file
diff --git a/src/Pages/Index.cshtml.cs b/src/Pages/Index.cshtml.cs
index 666c75e..bea663d 100644
--- a/src/Pages/Index.cshtml.cs
+++ b/src/Pages/Index.cshtml.cs
@@ -6,11 +6,11 @@ namespace I2R.LightNews.Pages;
public class IndexModel : PageModel
{
private readonly ILogger<IndexModel> _logger;
- private readonly GrabberService _grabber;
+ private readonly NrkNewsService _nrkNews;
- public IndexModel(ILogger<IndexModel> logger, GrabberService grabber) {
+ public IndexModel(ILogger<IndexModel> logger, NrkNewsService nrkNews) {
_logger = logger;
- _grabber = grabber;
+ _nrkNews = nrkNews;
}
public NewsSource FrontPage { get; set; }
@@ -25,7 +25,7 @@ public class IndexModel : PageModel
if (url.IsNullOrWhiteSpace()) {
FrontPage = site switch {
- "nrk" => await _grabber.GrabNrkAsync(),
+ "nrk" => await _nrkNews.GrabNrkAsync(),
_ => default
};
@@ -34,7 +34,7 @@ public class IndexModel : PageModel
}
} else {
Article = site switch {
- "nrk" => await _grabber.GrabNrkArticleAsync(url),
+ "nrk" => await _nrkNews.GrabNrkArticleAsync(url),
_ => default
};
diff --git a/src/Pages/NrkRadio.cshtml b/src/Pages/NrkRadio.cshtml
new file mode 100644
index 0000000..4679c0f
--- /dev/null
+++ b/src/Pages/NrkRadio.cshtml
@@ -0,0 +1,12 @@
+@page "/nrk-radio"
+@model I2R.LightNews.Pages.NrkRadio
+@{
+ Layout = "_Layout";
+}
+
+<aside id="app-data" style="display: none" aria-hidden="true" data-json="@(Model.FrontPageDataJSON)"></aside>
+<div id="app"></div>
+
+@section Scripts {
+ <script asp-append-version="true" src="~/radio.js"></script>
+} \ No newline at end of file
diff --git a/src/Pages/NrkRadio.cshtml.cs b/src/Pages/NrkRadio.cshtml.cs
new file mode 100644
index 0000000..f536c19
--- /dev/null
+++ b/src/Pages/NrkRadio.cshtml.cs
@@ -0,0 +1,13 @@
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.AspNetCore.Mvc.RazorPages;
+
+namespace I2R.LightNews.Pages;
+
+public class NrkRadio : PageModel
+{
+ public string FrontPageDataJSON { get; set; }
+
+ public ActionResult OnGet() {
+ return Page();
+ }
+} \ No newline at end of file
diff --git a/src/Pages/Shared/_Layout.cshtml b/src/Pages/Shared/_Layout.cshtml
index c3ca817..d9022fc 100644
--- a/src/Pages/Shared/_Layout.cshtml
+++ b/src/Pages/Shared/_Layout.cshtml
@@ -12,12 +12,7 @@
<nav>
<div class="left">
<a href="/nrk" class="@(Context.Request.Path.StartsWithSegments("/nrk") ? "active" : "")">NRK</a>
- <a href="/dagbladet" class="@(Context.Request.Path.StartsWithSegments("/dagbladet") ? "active" : "")">Dagbladet</a>
- <a href="/aftenposten" class="@(Context.Request.Path.StartsWithSegments("/aftenposten") ? "active" : "")">Aftenposten</a>
- <a href="/vg" class="@(Context.Request.Path.StartsWithSegments("/vg") ? "active" : "")">VG</a>
- <a href="/dn" class="@(Context.Request.Path.StartsWithSegments("/dn") ? "active" : "")">DN</a>
- <a href="/e24" class="@(Context.Request.Path.StartsWithSegments("/e24") ? "active" : "")">E24</a>
- <a href="/kode24" class="@(Context.Request.Path.StartsWithSegments("/kode24") ? "active" : "")">Kode 24</a>
+ <a href="/nrk-radio" class="@(Context.Request.Path.StartsWithSegments("/nrk-radio") ? "active" : "")">NRK Radio</a>
</div>
<div class="right">
<a href="/om" class="@(Context.Request.Path.StartsWithSegments("/om") ? "active" : "")">Om lettnytt</a>
diff --git a/src/Program.cs b/src/Program.cs
index 0b7930a..ba011a6 100644
--- a/src/Program.cs
+++ b/src/Program.cs
@@ -1,12 +1,16 @@
global using I2R.LightNews.Services;
global using I2R.LightNews.Models;
global using IOL.Helpers;
+using I2R.LightNews;
var builder = WebApplication.CreateBuilder(args);
-builder.Services.AddHttpClient();
+builder.Services.AddHttpClient<NrkRadioService>();
+builder.Services.AddHttpClient<NrkNewsService>();
builder.Services.AddMemoryCache();
-builder.Services.AddScoped<GrabberService>();
+builder.Services.AddScoped<NrkNewsService>();
+builder.Services.AddScoped<NrkRadioService>();
+builder.Services.AddControllers();
builder.Services.AddRazorPages().AddRazorRuntimeCompilation();
var app = builder.Build();
@@ -15,4 +19,6 @@ app.UseStaticFiles();
app.UseStatusCodePages();
app.UseRouting();
app.MapRazorPages();
+app.MapControllers();
+RadioIndexDb.CreateIfNotExists();
app.Run(); \ No newline at end of file
diff --git a/src/RadioIndexDb.cs b/src/RadioIndexDb.cs
new file mode 100644
index 0000000..f96c4af
--- /dev/null
+++ b/src/RadioIndexDb.cs
@@ -0,0 +1,57 @@
+using System.Text.Json;
+using Dapper;
+using I2R.LightNews.Utilities;
+using Microsoft.Data.Sqlite;
+
+namespace I2R.LightNews;
+
+public static class RadioIndexDb
+{
+ private static readonly string ConnectionString = "data source=AppData/radio-index.db";
+
+ public static void AddEntry(RadioSeries entry) {
+ using var db = new SqliteConnection(ConnectionString);
+ if (!db.TableExists("series")) return;
+ var addOperation = db.Execute(@"insert series(name,description,canonical_url,episodes) values (@name,@description,@canonical_url,@episodes)", new {
+ name = entry.Name,
+ description = entry.Description,
+ canonical_url = entry.CanonicalUri,
+ episodes = JsonSerializer.Serialize(entry.Episodes)
+ });
+ if (addOperation == 0) Console.WriteLine("No rows were added");
+ }
+
+ public static void DeleteEntry(int id) { }
+
+ public static RadioSeries GetEntry(int id) {
+ using var db = new SqliteConnection(ConnectionString);
+ if (!db.TableExists("series")) return default;
+ return db.QueryFirstOrDefault<RadioSeries>(@"select * from series where id=@id", new {id});
+ }
+
+ public static List<RadioSeries> GetEntries(string query, bool includeEpisodes = false) {
+ using var db = new SqliteConnection(ConnectionString);
+ if (!db.TableExists("series")) return default;
+ var selectSet = includeEpisodes ? "*" : "id,name,description,type,canonical_url";
+ var result = query.HasValue()
+ ? db.Query<RadioSeries>(@$"select {selectSet} from series where name like '@query' || description like '@query' order by name")
+ : db.Query<RadioSeries>(@$"select {selectSet} from series order by name");
+ return result.ToList();
+ }
+
+ public static void CreateIfNotExists() {
+ using var db = new SqliteConnection(ConnectionString);
+ if (!db.TableExists("series")) {
+ db.Execute(@"
+ create table series(
+ id integer primary key autoincrement,
+ name text,
+ description text,
+ type text,
+ canonical_url text,
+ episodes json
+ )
+ ");
+ }
+ }
+} \ No newline at end of file
diff --git a/src/Services/GrabberService.cs b/src/Services/NrkNewsService.cs
index d6650a2..df9d64e 100644
--- a/src/Services/GrabberService.cs
+++ b/src/Services/NrkNewsService.cs
@@ -6,9 +6,9 @@ using Microsoft.Extensions.Caching.Memory;
namespace I2R.LightNews.Services;
-public class GrabberService
+public class NrkNewsService
{
- private readonly ILogger<GrabberService> _logger;
+ private readonly ILogger<NrkNewsService> _logger;
private readonly IMemoryCache _memoryCache;
private readonly HttpClient _http;
private const string NrkPrefix = "nrkno";
@@ -18,7 +18,7 @@ public class GrabberService
HostPath = "AppData/__sitecache"
};
- public GrabberService(ILogger<GrabberService> logger, HttpClient http, IMemoryCache memoryCache) {
+ public NrkNewsService(ILogger<NrkNewsService> logger, HttpClient http, IMemoryCache memoryCache) {
_logger = logger;
_http = http;
_memoryCache = memoryCache;
@@ -100,7 +100,7 @@ public class GrabberService
result.Title = doc.QuerySelector(".article-feature__intro h1").TextContent;
var contentHtml = doc.QuerySelector(".article-feature__body").InnerHtml;
result.Content = HtmlSanitiser.SanitizeHtmlFragment(subtitle + contentHtml, string.Join(',', defaultExcludes));
- } else if (url.Contains("nrk.no/nyheter") || doc.QuerySelector(".bulletin-text") != default) {
+ } else if (url.Contains("nrk.no/nyheter") || (doc.QuerySelector(".bulletin-text") != default && doc.QuerySelector(".article-body") == defaultExcludes)) {
result.Content = HtmlSanitiser.SanitizeHtmlFragment(doc.QuerySelector(".bulletin-text").InnerHtml);
} else {
result.Content = HtmlSanitiser.SanitizeHtmlFragment(doc.QuerySelector(".article-body").InnerHtml, string.Join(',', defaultExcludes));
diff --git a/src/Services/NrkRadioService.cs b/src/Services/NrkRadioService.cs
new file mode 100644
index 0000000..a2889ce
--- /dev/null
+++ b/src/Services/NrkRadioService.cs
@@ -0,0 +1,31 @@
+using Microsoft.Extensions.Caching.Memory;
+
+namespace I2R.LightNews.Services;
+
+public class NrkRadioService
+{
+ private readonly IMemoryCache _cache;
+ private readonly HttpClient _http;
+ private const string CATEGORY_SEARCH_CACHE_KEY = "category_search";
+
+ public NrkRadioService(IMemoryCache cache, HttpClient http) {
+ _cache = cache;
+ http.BaseAddress = new Uri("https://psapi.nrk.no");
+ _http = http;
+ }
+
+ public async Task GetEverythingAsync() {
+ var path = "/radio/search/categories/alt-innhold";
+ var everything = new List<RadioSeries>();
+ while (path.HasValue()) {
+ var response = await _http.GetFromJsonAsync<RadioCategorySearchResult>(path);
+
+ }
+ }
+
+ public async Task<RadioCategorySearchResult> SearchCategoriesAsync(string query, int take = 50, int skip = 50) {
+ return await _http.GetFromJsonAsync<RadioCategorySearchResult>(
+ "/radio/search/categories/alt-innhold?q=" + query + "&take=" + take + "&skip=" + skip
+ );
+ }
+} \ No newline at end of file
diff --git a/src/Utilities/SqliteConnectionHelpers.cs b/src/Utilities/SqliteConnectionHelpers.cs
new file mode 100644
index 0000000..59fec5a
--- /dev/null
+++ b/src/Utilities/SqliteConnectionHelpers.cs
@@ -0,0 +1,14 @@
+using Dapper;
+using Microsoft.Data.Sqlite;
+
+namespace I2R.LightNews.Utilities;
+
+public static class SqliteConnectionHelpers
+{
+ public static bool TableExists(this SqliteConnection db, string tableName) {
+ return db.QueryFirstOrDefault<string>(
+ "select name from sqlite_master where type='table' and name=@tableName"
+ , new {tableName}
+ ).HasValue();
+ }
+} \ No newline at end of file
diff --git a/src/wwwroot/radio.js b/src/wwwroot/radio.js
new file mode 100644
index 0000000..d0e3fdd
--- /dev/null
+++ b/src/wwwroot/radio.js
@@ -0,0 +1,4 @@
+const app = document.getElementById("app");
+const appData = document.getElementById("app-data");
+
+app.innerText = "Laster...";