Audible API GET-Library serializes to new DTO

This commit is contained in:
Robert McRackan 2019-10-25 16:02:05 -04:00
parent 27ffad346f
commit 8e4dcb1780
11 changed files with 2078 additions and 69 deletions

View File

@ -7,7 +7,7 @@ using System.Threading.Tasks;
using AudibleApi;
using AudibleApi.Authentication;
using AudibleApi.Authorization;
using Newtonsoft.Json.Linq;
using DTOs;
namespace AudibleApiDomainService
{
@ -87,6 +87,46 @@ namespace AudibleApiDomainService
}
#endregion
public async Task ImportLibraryAsync()
{
try
{
var items = await GetLibraryItemsAsync();
//var (total, newEntries) = await ScrapingDomainServices.Indexer.IndexLibraryAsync(items);
}
catch (Exception ex)
{
// catch here for easier debugging
throw;
}
}
private async Task<List<Item>> GetLibraryItemsAsync()
{
var allItems = new List<Item>();
for (var i = 1; ; i++)
{
var page = await _api.GetLibraryAsync(new LibraryOptions
{
NumberOfResultPerPage = 1000,
PageNumber = i,
PurchasedAfter = new DateTime(2000, 1, 1),
ResponseGroups = LibraryOptions.ResponseGroupOptions.ALL_OPTIONS
});
// important! use this convert method
var libResult = LibraryApiV10.FromJson(page.ToString());
if (!libResult.Items.Any())
break;
allItems.AddRange(libResult.Items);
}
return allItems;
}
//public async Task DownloadBookAsync(string asinToDownload)
//{
// // console example
@ -107,40 +147,5 @@ namespace AudibleApiDomainService
// File.Delete(finalFile);
//}
public async Task ImportLibraryAsync()
{
// json = api.GetLibrary
var jObjects = await GetLibraryItemsAsync();
// json => DTOs
// indexer.update(DTOs)
}
private async Task<List<JObject>> GetLibraryItemsAsync()
{
var allJsonResults = new List<JObject>();
var pageNum = 1;
while (true)
{
var page = await _api.GetLibraryAsync(new LibraryOptions
{
NumberOfResultPerPage = 1000,
PageNumber = pageNum
});
var debugStr = page.ToString();
var items = page["items"].Cast<JObject>();
allJsonResults.AddRange(items);
if (!items.Any())
break;
pageNum++;
}
return allJsonResults;
}
}
}

View File

@ -48,15 +48,19 @@ namespace CookieMonster
value = Encoding.ASCII.GetString(decodedData);
}
col.Add(new CookieValue { Browser = "chrome", Domain = host_key, Name = name, Value = value, LastAccess = chromeTimeToDateTimeUtc(last_access_utc), Expires = chromeTimeToDateTimeUtc(expires_utc) });
try
{
// if something goes wrong in this step (eg: a cookie has an invalid filetime), then just skip this cookie
col.Add(new CookieValue { Browser = "chrome", Domain = host_key, Name = name, Value = value, LastAccess = chromeTimeToDateTimeUtc(last_access_utc), Expires = chromeTimeToDateTimeUtc(expires_utc) });
}
catch { }
}
return col;
}
// Chrome uses 1601-01-01 00:00:00 UTC as the epoch (ie the starting point for the millisecond time counter).
// this is the same as "FILETIME" in Win32 except FILETIME uses 100ns ticks instead of ms.
private static DateTime chromeTimeToDateTimeUtc(long time) => DateTime.SpecifyKind(DateTime.FromFileTime(time * 10), DateTimeKind.Utc);
}
// Chrome uses 1601-01-01 00:00:00 UTC as the epoch (ie the starting point for the millisecond time counter).
// this is the same as "FILETIME" in Win32 except FILETIME uses 100ns ticks instead of ms.
private static DateTime chromeTimeToDateTimeUtc(long time) => DateTime.SpecifyKind(DateTime.FromFileTime(time * 10), DateTimeKind.Utc);
}
}

View File

@ -32,5 +32,7 @@ namespace DTOs
public List<(string categoryId, string categoryName)> Categories { get; } = new List<(string categoryId, string categoryName)>();
public List<SeriesEntry> Series { get; } = new List<SeriesEntry>();
}
public override string ToString() => $"[{ProductId}] {Title}";
}
}

View File

@ -4,4 +4,8 @@
<TargetFramework>netstandard2.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\Dinah.Core\Dinah.Core\Dinah.Core.csproj" />
</ItemGroup>
</Project>

1829
DTOs/LibraryApiV10.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,140 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dinah.Core.Collections.Generic;
namespace DTOs
{
public partial class LibraryApiV10
{
public IEnumerable<Person> AuthorsDistinct => Items.GetAuthorsDistinct();
public IEnumerable<Person> NarratorsDistinct => Items.GetNarratorsDistinct();
public override string ToString() => $"{Items.Length} {nameof(Items)}, {ResponseGroups.Length} {nameof(ResponseGroups)}";
}
public partial class Item
{
public string ProductId => Asin;
public int LengthInMinutes => RuntimeLengthMin ?? 0;
public string Description => PublisherSummary;
public bool Episodes
=> Relationships
?.Where(r => r.RelationshipToProduct == RelationshipToProduct.Child && r.RelationshipType == RelationshipType.Episode)
.Any()
?? false;
public string PictureId => ProductImages?.PictureId;
public string SupplementUrls => PdfUrl.AbsoluteUri; // item.PdfUrl == item.PdfLink
public DateTime DateAdded => PurchaseDate.UtcDateTime;
public float Product_OverallStars => Convert.ToSingle(Rating?.OverallDistribution.DisplayStars ?? 0);
public float Product_PerformanceStars => Convert.ToSingle(Rating?.PerformanceDistribution.DisplayStars ?? 0);
public float Product_StoryStars => Convert.ToSingle(Rating?.StoryDistribution.DisplayStars ?? 0);
public int MyUserRating_Overall => Convert.ToInt32(ProvidedReview?.Ratings.OverallRating ?? 0L);
public int MyUserRating_Performance => Convert.ToInt32(ProvidedReview?.Ratings.PerformanceRating ?? 0L);
public int MyUserRating_Story => Convert.ToInt32(ProvidedReview?.Ratings.StoryRating ?? 0L);
public bool IsAbridged
=> FormatType.HasValue
? FormatType == DTOs.FormatType.Abridged
: false;
public DateTime? DatePublished => IssueDate?.UtcDateTime; // item.IssueDate == item.ReleaseDate
public string Publisher => PublisherName;
// future: need support for multiple categories
public Ladder[] Categories => CategoryLadders?.FirstOrDefault()?.Ladder ?? new Ladder[0];
// LibraryDTO.DownloadBookLink will be handled differently. see api.DownloadAaxWorkaroundAsync(asin)
public IEnumerable<Person> AuthorsDistinct => Authors.DistinctBy(a => new { a.Name, a.Asin });
public IEnumerable<Person> NarratorsDistinct => Narrators.DistinctBy(a => new { a.Name, a.Asin });
public override string ToString() => $"[{ProductId}] {Title}";
}
public partial class Person
{
public override string ToString() => $"{Name}";
}
public partial class AvailableCodec
{
public override string ToString() => $"{Name} {Format} {EnhancedCodec}";
}
public partial class CategoryLadder
{
public override string ToString() => Ladder.Select(l => l.CategoryName).Aggregate((a, b) => $"{a} | {b}");
}
public partial class Ladder
{
public string CategoryId => Id;
public string CategoryName => Name;
public override string ToString() => $"[{CategoryId}] {CategoryName}";
}
public partial class ContentRating
{
public override string ToString() => $"{Steaminess}";
}
public partial class Review
{
public override string ToString() => $"{this.Title}";
}
public partial class GuidedResponse
{
//public override string ToString() =>
}
public partial class Ratings
{
public override string ToString() => $"{OverallRating:0.0}|{PerformanceRating:0.0}|{StoryRating:0.0}";
}
public partial class ReviewContentScores
{
public override string ToString() => $"Helpful={NumHelpfulVotes}, Unhelpful={NumUnhelpfulVotes}";
}
public partial class Plan
{
public override string ToString() => $"{PlanName}";
}
public partial class Price
{
public override string ToString() => $"List={ListPrice}, Lowest={LowestPrice}";
}
public partial class ListPriceClass
{
public override string ToString() => $"{Base}";
}
public partial class ProductImages
{
public string PictureId
=> The500
.AbsoluteUri // https://m.media-amazon.com/images/I/51T1NWIkR4L._SL500_.jpg?foo=bar
?.Split('/').Last() // 51T1NWIkR4L._SL500_.jpg?foo=bar
?.Split('.').First() // 51T1NWIkR4L
;
public override string ToString() => $"{The500}";
}
public partial class Rating
{
public override string ToString() => $"{OverallDistribution}|{PerformanceDistribution}|{StoryDistribution}";
}
public partial class Distribution
{
public override string ToString() => $"{DisplayStars:0.0}";
}
public partial class Relationship
{
public override string ToString() => $"{RelationshipToProduct} {RelationshipType}";
}
public partial class Series
{
public string SeriesName => Title;
public string SeriesId => Asin;
public float Index
=> string.IsNullOrEmpty(Sequence)
? 0
// eg: a book containing volumes 5,6,7,8 has sequence "5-8"
: float.Parse(Sequence.Split('-').First());
public override string ToString() => $"[{SeriesId}] {SeriesName}";
}
}

View File

@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dinah.Core.Collections.Generic;
namespace DTOs
{
public static class LibraryApiV10Extensions
{
public static IEnumerable<Person> GetAuthorsDistinct(this IEnumerable<Item> items)
=> items.SelectMany(i => i.Authors).DistinctBy(a => new { a.Name, a.Asin });
public static IEnumerable<Person> GetNarratorsDistinct(this IEnumerable<Item> items)
=> items.SelectMany(i => i.Narrators).DistinctBy(a => new { a.Name, a.Asin });
}
}

View File

@ -15,7 +15,7 @@ namespace LibationWinForm
public partial class Form1 : Form
{
// initial call here will initiate config loading
private Configuration config = Configuration.Instance;
private Configuration config { get; } = Configuration.Instance;
private string backupsCountsLbl_Format;
private string pdfsCountsLbl_Format;

View File

@ -371,7 +371,7 @@ namespace ScrapingDomainServices
{
var series
= context.Series.Local.SingleOrDefault(c => c.AudibleSeriesId == seriesId)
?? context.Series.Add(new Series(new AudibleSeriesId(seriesId))).Entity;
?? context.Series.Add(new DataLayer.Series(new AudibleSeriesId(seriesId))).Entity;
series.UpdateName(seriesName);
}
}

View File

@ -18,8 +18,8 @@ LocalDb database files live at:
Migrations
==========
Visual Studio, EF Core 2.0
--------------------------
Visual Studio, EF Core
----------------------
View > Other Windows > Package Manager Console
Default project: DataLayer
Startup project: DataLayer

View File

@ -1,14 +1,12 @@
-- begin CONFIG FILES ---------------------------------------------------------------------------------------------------------------------
try saving back into config
.\appsettings.json should only be a pointer to the real settings file location: LibationSettings.json
replace complex config saving throughout with new way in my ConsoleDependencyInjection solution
all settings should be strongly typed
re-create my shortcuts and bak
multiple files named "appsettings.json" will overwrite each other
libraries should avoid this generic name. ok for applications to use them
LibationSettings.json => appsettings.json
also, move these settings into a strong type
re-create my shortcuts and bak
Audible API
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\audible api\AudibleApi\_Tests\AudibleApi.Tests\bin\Debug\netcoreapp3.0\L1
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\audible api\AudibleApi\_Tests\AudibleApi.Tests\bin\Debug\netcoreapp3.0\ComputedTestValues
@ -31,6 +29,9 @@ replace all scraping with audible api
using var pageRetriever = websiteProcessorControl1.GetPageRetriever();
jsonFilepaths = await DownloadLibrary.DownloadLibraryAsync(pageRetriever).ConfigureAwait(false);
move old DTOs back into scraping so it's easier to move them all to new legacy area
library to return strongly typed LibraryApiV10
note: some json importing may still be relevant. might want to allow custom importing
scraping stuff to remove:
@ -60,13 +61,20 @@ ADDED PROJECT REFERENCES
3.1 Domain Internal Utilities
InternalUtilities
DTOs
Audible API. GET /1.0/library , GET /1.0/library/{asin}
TONS of expensive conversion: GetLibraryAsync > string > JObject > string > LibraryApiV10
-- end REPLACE SCRAPING WITH API ---------------------------------------------------------------------------------------------------------------------
-- begin LEGACY ---------------------------------------------------------------------------------------------------------------------
create legacy space to hold legacy code
-- begin ENHANCEMENT, CATEGORIES ---------------------------------------------------------------------------------------------------------------------
add support for multiple categories
-- end ENHANCEMENT, CATEGORIES ---------------------------------------------------------------------------------------------------------------------
scraping code
"legacy inAudible wire-up code"
-- begin LEGACY ---------------------------------------------------------------------------------------------------------------------
retain legacy functionality in out-of-the-way area. turn on/off in settings?
> scraping code
> "legacy inAudible wire-up code"
-- end LEGACY ---------------------------------------------------------------------------------------------------------------------
-- begin CLEAN UP ARCHITECTURE ---------------------------------------------------------------------------------------------------------------------
@ -76,18 +84,19 @@ my ui sucks. it's also tightly coupled with biz logic. can't replace ui until bi
-- begin UNIT TESTS ---------------------------------------------------------------------------------------------------------------------
all "UNTESTED" code needs unit tests
Turn into unit tests or demos
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Dinah.Core\Dinah.Core.Windows.Forms\UNTESTED\TextBoxBaseTextWriter.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Dinah.Core\Dinah.Core\UNTESTED\EnumerationExamples.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Dinah.Core\Dinah.Core\UNTESTED\EnumExt.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Dinah.Core\Dinah.Core\UNTESTED\_Collections\Generic\IEnumerable[T]Ext.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Dinah.Core\Dinah.Core\UNTESTED\_IO\MultiTextWriter.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Libation\AudibleDotComAutomation\UNTESTED\Selenium.Examples.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Libation\DataLayer\UNTESTED\_scratch pad\ScratchPad.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Libation\LibationWinForm\UNTESTED\BookLiberation\ProcessorAutomationController.Examples.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Libation\Scraping\UNTESTED\Rules\ScraperRules.Examples.cs
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Libation\Scraping\UNTESTED\Selectors\ByFactory.Example.cs
search 'example code' on:
C:\Dropbox\Dinah's folder\coding\_NET\Visual Studio 2019\Libation\LibationWinForm\UNTESTED\Form1.cs
TextBoxBaseTextWriter.cs
EnumerationExamples.cs
EnumExt.cs
IEnumerable[T]Ext.cs
MultiTextWriter.cs
Selenium.Examples.cs
ScratchPad.cs
ProcessorAutomationController.Examples.cs
ScraperRules.Examples.cs
ByFactory.Example.cs
search 'example code' on: LibationWinForm\...\Form1.cs
EnumerationFlagsExtensions.EXAMPLES()
// examples
-- end UNIT TESTS ---------------------------------------------------------------------------------------------------------------------
-- begin DECRYPTING ---------------------------------------------------------------------------------------------------------------------