Skip to content

Commit

Permalink
Initial application
Browse files Browse the repository at this point in the history
  • Loading branch information
gitfool committed Jan 1, 2019
1 parent 5b68893 commit 35c2123
Show file tree
Hide file tree
Showing 30 changed files with 1,597 additions and 0 deletions.
14 changes: 14 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
root = true

[*]
indent_style = space
indent_size = 4
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.{config,csproj,json,props,xml,yml}]
indent_size = 2

[*.cs]
dotnet_sort_system_directives_first = false
17 changes: 17 additions & 0 deletions Application/Application.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.2</TargetFramework>

<AssemblyName>BoardGameGeek.Dungeon</AssemblyName>
<RootNamespace>BoardGameGeek.Dungeon</RootNamespace>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="PocketLogger.Subscribe" Version="0.6.1" />
<PackageReference Include="System.CommandLine.Experimental" Version="0.1.0-alpha-63625-01" />
<PackageReference Include="System.Interactive" Version="3.2.0" />
</ItemGroup>
</Project>
95 changes: 95 additions & 0 deletions Application/BggService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
using Pocket;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Formatting;
using System.Threading.Tasks;
using static Pocket.Logger<BoardGameGeek.Dungeon.BggService>;
// ReSharper disable StringLiteralTypo

namespace BoardGameGeek.Dungeon
{
public interface IBggService
{
Task<ThingItems> DownloadThingsAsync(IEnumerable<int> ids);
Task<CollectionItems> DownloadUserCollectionAsync(string userName);
Task<UserPlays> DownloadUserPlaysAsync(string userName, int? year = null, int? id = null, int? page = null);
}

public sealed class BggService : IBggService
{
public async Task<ThingItems> DownloadThingsAsync(IEnumerable<int> ids)
{
var query = $"id={string.Join(",", ids)}";
var uri = new Uri($"https://boardgamegeek.com/xmlapi2/thing?{query}");
Log.Info($" {uri}");
return await DownloadAsync<ThingItems>(uri);
}

public async Task<CollectionItems> DownloadUserCollectionAsync(string userName)
{
var query = $"username={userName}&subtype=boardgame&minplays=1";
var uri = new Uri($"https://boardgamegeek.com/xmlapi2/collection?{query}");
Log.Info($" {uri}");
await Task.Delay(TimeSpan.FromSeconds(5));
var response = await HttpClient.GetAsync(uri);
response.EnsureSuccessStatusCode();
if (response.StatusCode == HttpStatusCode.Accepted || response.StatusCode == HttpStatusCode.TooManyRequests)
{
// collection requests are queued; http://boardgamegeek.com/wiki/page/BGG_XML_API2#toc11
using (var delay = EnumerateDelay().GetEnumerator())
{
do
{
delay.MoveNext();
await Task.Delay(delay.Current);
response = await HttpClient.GetAsync(uri);
response.EnsureSuccessStatusCode();
}
while (response.StatusCode == HttpStatusCode.Accepted || response.StatusCode == HttpStatusCode.TooManyRequests);
}
}
return await response.Content.ReadAsAsync<CollectionItems>(XmlFormatterCollection);
}

public async Task<UserPlays> DownloadUserPlaysAsync(string userName, int? year = null, int? id = null, int? page = null)
{
var query = $"username={userName}&subtype=boardgame";
if (id != null)
{
query += $"&id={id}";
}
if (year != null)
{
query += $"&mindate={year}-01-01&maxdate={year}-12-31";
}
if (page != null)
{
query += $"&page={page}";
}
var uri = new Uri($"https://boardgamegeek.com/xmlapi2/plays?{query}");
Log.Info($" {uri}");
return await DownloadAsync<UserPlays>(uri);
}

private async Task<T> DownloadAsync<T>(Uri uri)
{
await Task.Delay(TimeSpan.FromSeconds(5));
var response = await HttpClient.GetAsync(uri);
response.EnsureSuccessStatusCode();
return await response.Content.ReadAsAsync<T>(XmlFormatterCollection);
}

private static IEnumerable<TimeSpan> EnumerateDelay()
{
return Enumerable.Range(3, 5).Select(seconds => TimeSpan.FromSeconds(Math.Pow(2, seconds)))
.Concat(Enumerable.Repeat(TimeSpan.FromMinutes(1), int.MaxValue));
}

private static readonly HttpClient HttpClient = new HttpClient();
private static readonly XmlMediaTypeFormatter XmlFormatter = new XmlMediaTypeFormatter { UseXmlSerializer = true };
private static readonly MediaTypeFormatterCollection XmlFormatterCollection = new MediaTypeFormatterCollection(new MediaTypeFormatter[] { XmlFormatter });
}
}
17 changes: 17 additions & 0 deletions Application/CollectionDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace BoardGameGeek.Dungeon
{
public class CollectionDto
{
public int GameId { get; set; }
public string GameName { get; set; }
public string Image { get; set; }
public string Thumbnail { get; set; }
public int TotalPlays { get; set; }
public string Comments { get; set; }

public override string ToString()
{
return $"{GameName} (\u03a3 {TotalPlays}x)";
}
}
}
160 changes: 160 additions & 0 deletions Application/CollectionXml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
using System.Text;
using System.Xml.Serialization;
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedMember.Global

namespace BoardGameGeek.Dungeon
{
[XmlRoot("items")]
public class CollectionItems
{
[XmlAttribute("totalitems")]
public int TotalItems { get; set; }

[XmlAttribute("termsofuse")]
public string TermsOfUse { get; set; }

[XmlAttribute("pubdate")]
public string PubDate { get; set; }

[XmlElement("item")]
public CollectionItem[] Items { get; set; }
}

public class CollectionItem
{
[XmlAttribute("objecttype")]
public string ObjectType { get; set; }

[XmlAttribute("objectid")]
public int ObjectId { get; set; }

[XmlAttribute("subtype")]
public string SubType { get; set; }

[XmlElement("name")]
public string Name { get; set; }

[XmlElement("yearpublished")]
public int YearPublished { get; set; }

[XmlElement("image")]
public string Image { get; set; }

[XmlElement("thumbnail")]
public string Thumbnail { get; set; }

[XmlElement("numplays")]
public int TotalPlays { get; set; }

[XmlElement("comment")]
public string Comments { get; set; }

[XmlElement("status")]
public CollectionItemStatus Status { get; set; }

public override string ToString()
{
return $"Name = {Name}, TotalPlays = {TotalPlays}";
}
}

public class CollectionItemStatus
{
[XmlAttribute("own")]
public bool Own { get; set; }

[XmlAttribute("prevowned")]
public bool PrevOwned { get; set; }

[XmlAttribute("fortrade")]
public bool ForTrade { get; set; }

[XmlAttribute("want")]
public bool WantInTrade { get; set; }

[XmlAttribute("wanttoplay")]
public bool WantToPlay { get; set; }

[XmlAttribute("wanttobuy")]
public bool WantToBuy { get; set; }

[XmlAttribute("wishlist")]
public bool WishList { get; set; }

[XmlAttribute("wishlistpriority")]
public int WishListPriority { get; set; }

[XmlAttribute("preordered")]
public bool Preordered { get; set; }

[XmlAttribute("lastmodified")]
public string LastModified { get; set; }

public override string ToString()
{
var builder = new StringBuilder();
if (Own)
{
builder.Append(nameof(Own));
}
if (PrevOwned)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(nameof(PrevOwned));
}
if (ForTrade)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(nameof(ForTrade));
}
if (WantInTrade)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(nameof(WantInTrade));
}
if (WantToPlay)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(nameof(WantToPlay));
}
if (WantToBuy)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(nameof(WantToBuy));
}
if (WishList)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append($"{nameof(WishList)}({WishListPriority})");
}
if (Preordered)
{
if (builder.Length > 0)
{
builder.Append(", ");
}
builder.Append(nameof(Preordered));
}
return builder.ToString();
}
}
}
57 changes: 57 additions & 0 deletions Application/CommandLine.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
using System;
using System.CommandLine;
using System.CommandLine.Builder;
using System.CommandLine.Invocation;
using System.Threading.Tasks;

namespace BoardGameGeek.Dungeon
{
public class CommandLine
{
static CommandLine()
{
// arguments
var userNameArgument = new Argument<string> { Name = "username", Description = "Geek user name." };

// options
var allOption = new Option(new[] { "-a", "--all" }, "Analyze all override.", new Argument<bool>());
var yearOption = new Option(new[] { "-y", "--year" }, "Year to analyze. Defaults to current year.", new Argument<int>(DateTime.Now.Year));

// parser
Parser = new CommandLineBuilder(new RootCommand("A command line tool for interacting with the BoardGameGeek API")
{
new Command("plays", "Get user plays.", new[] { allOption, yearOption }, userNameArgument,
handler: CommandHandler.Create(typeof(CommandLine).GetMethod(nameof(PlaysAsync)))),
new Command("stats", "Get user stats.", new[] { allOption, yearOption }, userNameArgument,
handler: CommandHandler.Create(typeof(CommandLine).GetMethod(nameof(StatsAsync))))
})
.UseDefaults()
.Build();
}

// commands
public static async Task PlaysAsync(string userName, bool all, int? year)
{
if (all)
{
year = null;
}
var processor = new Processor(new BggService());
var renderer = new Renderer();
await renderer.RenderPlays(userName, year, await processor.ProcessPlays(userName, year));
}

public static async Task StatsAsync(string userName, bool all, int? year)
{
if (all)
{
year = null;
}
var processor = new Processor(new BggService());
var renderer = new Renderer();
await renderer.RenderStats(userName, year, await processor.ProcessStats(userName, year));
}

public static Parser Parser { get; }
}
}
22 changes: 22 additions & 0 deletions Application/GameDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
namespace BoardGameGeek.Dungeon
{
public class GameDto
{
public int Id { get; set; }
public string Name { get; set; }
public bool IsExpansion { get; set; }
public int ParentId { get; set; }
public int Plays { get; set; }
public int Sessions { get; set; }
public int CumulativePlays { get; set; }
public int CumulativeSessions { get; set; }
public bool IsHighlight { get; set; }
public bool IsNew { get; set; }
public string Comments { get; set; }

public override string ToString()
{
return $"{Plays}x {Name} (\u03a3 {CumulativePlays}x)";
}
}
}
Loading

0 comments on commit 35c2123

Please sign in to comment.