diff options
| author | ivarlovlie <git@ivarlovlie.no> | 2022-12-02 04:04:42 +0100 |
|---|---|---|
| committer | ivarlovlie <git@ivarlovlie.no> | 2022-12-02 04:04:42 +0100 |
| commit | 623a45d1ec1f7e636defd139b35b615b1a64af91 (patch) | |
| tree | 0e5c2d5f1e96cd6f4adb305ed3f35dd02f2485ee | |
| parent | a453135b470565c56df2fd319dc927db67e299c6 (diff) | |
| download | lettnytt-623a45d1ec1f7e636defd139b35b615b1a64af91.tar.xz lettnytt-623a45d1ec1f7e636defd139b35b615b1a64af91.zip | |
feat: !WIP nrk radio
| -rw-r--r-- | src/Endpoints/EndpointBase.cs | 6 | ||||
| -rw-r--r-- | src/Endpoints/RadioSearchEndpoint.cs | 17 | ||||
| -rw-r--r-- | src/I2R.LightNews.csproj | 2 | ||||
| -rw-r--r-- | src/Models/RadioCategorySearchResult.cs | 58 | ||||
| -rw-r--r-- | src/Models/RadioSeries.cs | 18 | ||||
| -rw-r--r-- | src/Pages/Index.cshtml | 82 | ||||
| -rw-r--r-- | src/Pages/Index.cshtml.cs | 10 | ||||
| -rw-r--r-- | src/Pages/NrkRadio.cshtml | 12 | ||||
| -rw-r--r-- | src/Pages/NrkRadio.cshtml.cs | 13 | ||||
| -rw-r--r-- | src/Pages/Shared/_Layout.cshtml | 7 | ||||
| -rw-r--r-- | src/Program.cs | 10 | ||||
| -rw-r--r-- | src/RadioIndexDb.cs | 57 | ||||
| -rw-r--r-- | src/Services/NrkNewsService.cs (renamed from src/Services/GrabberService.cs) | 8 | ||||
| -rw-r--r-- | src/Services/NrkRadioService.cs | 31 | ||||
| -rw-r--r-- | src/Utilities/SqliteConnectionHelpers.cs | 14 | ||||
| -rw-r--r-- | src/wwwroot/radio.js | 4 |
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..."; |
