コード例 #1
0
ファイル: PlaysController.cs プロジェクト: vfportero/bgg-json
        public async Task<List<PlayItem>> Get(string username)
        {
            var cachedResult = Cache.Default.Get(Cache.PlaysKey(username)) as List<PlayItem>;
            if (cachedResult != null)
            {
                Debug.WriteLine("Served Plays from Cache.");
                return cachedResult;
            }

            var client = new BoardGameGeekClient();

            var plays = await client.LoadPlays(username);
            var gameIds = new HashSet<int>(plays.Items.Select(g => g.GameId));
            var gameDetails = await client.ParallelLoadGames(gameIds);
            var gameDetailsById = gameDetails.ToDictionary(g => g.GameId);

            var response = (from p in plays.Items
                            orderby p.PlayDate descending
                            let g = gameDetailsById[p.GameId]
                            select new PlayItem
                           {
                               GameId = p.GameId,
                               Name = p.Name,
                               Image = g.Image,
                               Thumbnail = g.Thumbnail,
                               PlayDate = p.PlayDate,
                               NumPlays = p.NumPlays,
                               Comments = p.Comments
                           }).ToList();
            Cache.Default.Set(Cache.PlaysKey(username), response, DateTimeOffset.Now.AddSeconds(15));

            return response;
        }
コード例 #2
0
        public void CleanUpExistingRecords()
        {
            using (NemeStatsDbContext dbContext = new NemeStatsDbContext())
            {
                using (NemeStatsDataContext dataContext = new NemeStatsDataContext(dbContext, new SecuredEntityValidatorFactory()))
                {
                    var games = dataContext.GetQueryable <GameDefinition>()
                                .Where(game => game.BoardGameGeekGameDefinitionId == null)
                                .ToList();

                    var bggSearcher = new BoardGameGeekClient(new ApiDownloaderService());
                    int updateCount = 0;

                    foreach (GameDefinition game in games)
                    {
                        var bggResults = bggSearcher.SearchBoardGames(game.Name.Trim(), true);

                        if (bggResults.Count() == 1)
                        {
                            game.BoardGameGeekGameDefinitionId = bggResults.First().BoardGameId;
                            ApplicationUser user = new ApplicationUser
                            {
                                CurrentGamingGroupId = game.GamingGroupId
                            };
                            dataContext.Save(game, user);
                            dataContext.CommitAllChanges();
                            Console.WriteLine(game.Name + " had exactly one match and was updated.");
                            updateCount++;
                        }
                    }

                    Console.WriteLine("Updated " + updateCount + " records.");
                }
            }
        }
コード例 #3
0
ファイル: HotController.cs プロジェクト: vfportero/bgg-json
        public async Task<List<HotGame>> Get()
        {
            var cachedResult = Cache.Default.Get("Hotness") as List<HotGame>;
            if (cachedResult != null)
            {
                Debug.WriteLine("Served Hotness from Cache.");
                return cachedResult;
            }
            
            var client = new BoardGameGeekClient();

            var hotness = (await client.LoadHotness()).ToList();
            Cache.Default.Set("Hotness", hotness, DateTimeOffset.Now.AddSeconds(300));

            return hotness;
        }
コード例 #4
0
ファイル: ThingController.cs プロジェクト: vfportero/bgg-json
        public async Task<GameDetails> Get(int id)
        {
            var cachedResult = Cache.Default.Get(Cache.ThingKey(id)) as GameDetails;
            if (cachedResult != null)
            {
                Debug.WriteLine("Served Thing from Cache.");
                return cachedResult;
            }

            BoardGameGeekClient client = new BoardGameGeekClient();
            
            var thing = await client.LoadGame(id, false);

            Cache.Default.Set(Cache.ThingKey(id), thing, DateTimeOffset.Now.AddSeconds(15));
            return thing;
        }
コード例 #5
0
ファイル: ThingController.cs プロジェクト: n0th/ludusBBG
        public async Task <GameDetails> Get(int id)
        {
            var cachedResult = Cache.Default.Get(Cache.ThingKey(id)) as GameDetails;

            if (cachedResult != null)
            {
                Debug.WriteLine("Served Thing from Cache.");
                return(cachedResult);
            }

            BoardGameGeekClient client = new BoardGameGeekClient();

            var thing = await client.LoadGame(id, false);

            Cache.Default.Set(Cache.ThingKey(id), thing, DateTimeOffset.Now.AddSeconds(15));
            return(thing);
        }
コード例 #6
0
ファイル: HotController.cs プロジェクト: n0th/ludusBBG
        public async Task <List <HotGame> > Get()
        {
            var cachedResult = Cache.Default.Get("Hotness") as List <HotGame>;

            if (cachedResult != null)
            {
                Debug.WriteLine("Served Hotness from Cache.");
                return(cachedResult);
            }

            var client = new BoardGameGeekClient();

            var hotness = (await client.LoadHotness()).ToList();

            Cache.Default.Set("Hotness", hotness, DateTimeOffset.Now.AddSeconds(300));

            return(hotness);
        }
コード例 #7
0
        public void UpdateAllBoardGameGeekGameDefinitionData()
        {
            using (NemeStatsDbContext nemeStatsDbContext = new NemeStatsDbContext())
            {
                using (var dataContext = new NemeStatsDataContext(nemeStatsDbContext, new SecuredEntityValidatorFactory()))
                {
                    var apiDownloaderService = new ApiDownloaderService();
                    //API failures won't get logged!
                    var rollbarClient       = MockRepository.GenerateMock <IRollbarClient>();
                    var boardGameGeekClient = new BoardGameGeekClient(apiDownloaderService, rollbarClient);
                    var batchUpdateService  = new BoardGameGeekBatchUpdateJobService(dataContext, boardGameGeekClient, rollbarClient);

                    var totalRecordsUpdated = batchUpdateService.RefreshAllBoardGameGeekData();

                    Debug.WriteLine("Updated {0} total BoardGameGeekGameDefinition records.", totalRecordsUpdated);
                }
            }
        }
コード例 #8
0
ファイル: PlaysController.cs プロジェクト: melisek/bgg-json
        public async Task <List <PlayItem> > GetMostPlayedGames(string username, DateTime?from = null, DateTime?to = null)
        {
            var cachedResult = Cache.Default.Get(Cache.PlaysKey(username, null, from, to)) as List <PlayItem>;

            if (cachedResult != null)
            {
                Debug.WriteLine("Served Plays from Cache.");
                return(cachedResult);
            }

            var client = new BoardGameGeekClient();

            var plays = await client.LoadMostPlayedGames(username, 1, from, to);

            var gameIds     = new HashSet <int>(plays.Items.Select(g => g.GameId));
            var gameDetails = await client.ParallelLoadGames(gameIds);

            var gameDetailsById = gameDetails.ToDictionary(g => g.GameId);

            var response = (from p in plays.Items
                            orderby p.PlayDate descending
                            let g = gameDetailsById[p.GameId]
                                    select new PlayItem
            {
                GameId = p.GameId,
                Name = p.Name,
                Image = g.Image,
                Thumbnail = g.Thumbnail,
                PlayDate = p.PlayDate,
                NumPlays = p.NumPlays,
                Comments = p.Comments,
                Players = p.Players
            }).ToList();

            Cache.Default.Set(Cache.PlaysKey(username), response, DateTimeOffset.Now.AddSeconds(15));

            return(response);
        }
コード例 #9
0
        public async Task <BoardGameLink> Get(int id)
        {
            var cachedResult = Cache.Default.Get(Cache.ThingKey(id)) as BoardGameLink;

            if (cachedResult != null)
            {
                Debug.WriteLine("Served Thing from Cache.");
                return(cachedResult);
            }

            BoardGameGeekClient client = new BoardGameGeekClient();

            var thing = await client.LoadGame(id, false);

            var result = new BoardGameLink {
                Name   = thing.Name,
                GameId = thing.GameId,
                Html   = $@"<div class='game-list game-data-grid'><div title='Játékosok száma'><div class='game-icon icon-players' aria-hidden='true'></div><span class='game-data'>{thing.MinPlayers}-{thing.MaxPlayers} játékos</span></div><div title='Játékidő'><div class='game-icon icon-time' aria-hidden='true'></div><span class='game-data'>{thing.MinPlayTime}-{thing.MaxPlayTime} perc játékidő</span></div><div title='Korosztály'><div class='game-icon icon-age' aria-hidden='true'></div><span class='game-data'>{thing.MinAge} éves kortól</span></div><div title='BGG értékelés'><div class='game-icon icon-rating' aria-hidden='true'></div><a href='https://boardgamegeek.com/boardgame/{thing.GameId}' target='_blank' title='BGG adatlap'><span class='game-data'>{Math.Round(thing.AverageRating,1)}/10</span></a></div></div>"
            };

            Cache.Default.Set(Cache.ThingKey(id), thing, DateTimeOffset.Now.AddSeconds(15));
            return(result);
        }
コード例 #10
0
        public void ItGetsResultsForExistingGameNames()
        {
            using (NemeStatsDbContext dbContext = new NemeStatsDbContext())
            {
                using (NemeStatsDataContext dataContext = new NemeStatsDataContext())
                {
                    //gets a distinct list of all game names
                    //var gameNames2 = dataContext.GetQueryable<GameDefinition>()
                    //                           .GroupBy(game => game.Name)
                    //                           .Select(game => game.FirstOrDefault())
                    //                           .ToList();

                    var gameNames = new List <string>
                    {
                        "7 Wonders",
                        "Citadels",
                        "Cosmic encounter ",
                        "Coup",
                        "Dead of winter",
                        "Dominion",
                        "Five Tribes",
                        "Samurai Sword",
                        "Suburbia",
                        "Agricola",
                        "Arkham Horror",
                        "Bang!",
                        "Carcassonne",
                        "Castle Ravenloft",
                        "Race For The Galaxy",
                        "Settlers of Catan",
                        "Shadows of Brimstone",
                        "Sushi Go!",
                        "Twilight Imperium",
                        "Apples to Apples Kids",
                        "At the Gates of Loyang",
                        "Avalon",
                        "Boggle",
                        "Bohnanza",
                        "Carson City",
                        "Caverna",
                        "Chess",
                        "Cinque Terre",
                        "Clue",
                        "Coin Age",
                        "Cranium",
                        "Cranium Zigity",
                        "Cribbage",
                        "Cthulhu Gloom",
                        "Dominoes",
                        "Dungeon Fighter",
                        "Dungeon Petz",
                        "Dungeon Roll",
                        "Dutch Blitz",
                        "Eight Minute Empire",
                        "Famiglia",
                        "Fearsome Floors",
                        "Forbidden Island",
                        "Formula D",
                        "Freedom: The Underground Railroad",
                        "Galaxy Trucker",
                        "Game of thrones boardgame",
                        "Guillotine",
                        "Hive",
                        "Jaipur",
                        "Kaosball",
                        "Kemet",
                        "King of Tokyo",
                        "Legend of Drizzt",
                        "Legends of Andor",
                        "Lewis & Clark",
                        "Lords of Waterdeep",
                        "Love Letter",
                        "Machi Koro",
                        "Mage Knight Board Game",
                        "Mage Knight Dungeons",
                        "Magic the Gathering",
                        "Magic: The Gathering",
                        "Mascarade",
                        "Merchants & Marauders",
                        "Nations",
                        "Nuts",
                        "Once Upon a Time",
                        "Pairs (Calamity Variant)",
                        "Pandemic",
                        "Pathfinder ACG",
                        "Pirate Fluxx",
                        "Puerto Rico",
                        "Quantum",
                        "Quiddler",
                        "Quoridor",
                        "Race  for the galaxy",
                        "Rampage",
                        "Risk: The Lord of the Rings Trilogy Edition",
                        "Risk: The Walking Dead",
                        "RoboRally",
                        "Rummikub",
                        "Saboteur",
                        "Saboteur 2",
                        "Samurai Swords",
                        "SanGuoSha: Legends of the Three Kingdoms",
                        "Scattergories",
                        "Scotland yard",
                        "Scrabble",
                        "Settlers of America: Trails to Rails",
                        "Settlers of the Stone Age",
                        "Seven Wonders",
                        "Small World",
                        "Spartacus",
                        "Splendor",
                        "Star Realms",
                        "Star Trek Battle Force",
                        "Star Trek: Fleet Captains",
                        "Star Wars: X-Wing Miniatures Game",
                        "Survive!",
                        "Taboo",
                        "Tales of the Arabian Nights",
                        "The Little Prince",
                        "The Princes of Florence",
                        "The Resistance",
                        "Thunderstone Advance: Towers of Ruin",
                        "Ticket to Ride",
                        "Ticket to Ride Europe",
                        "Tiny Epic Kingdoms",
                        "Troyes",
                        "tzolkin",
                        "Warhammer 40K",
                        "Wits & Wagers",
                        "Wrath of Ashardalon",
                        "Zombicide"
                    };

                    var bggSearcher = new BoardGameGeekClient(new ApiDownloaderService());

                    foreach (string gameName in gameNames)
                    {
                        var results = bggSearcher.SearchBoardGames(gameName, true);

                        if (results.Count() != 1)
                        {
                            Console.WriteLine(gameName + " has " + results.Count() + " results.");
                        }
                    }
                }
            }
        }
コード例 #11
0
        public async Task <List <CollectionItem> > Get(string username, bool grouped = false, bool details = false)
        {
            var local        = Request.IsLocal();
            var cachedResult = Cache.Default.Get(Cache.CollectionKey(username, grouped, details)) as List <CollectionItem>;

            if (cachedResult != null && !local)
            {
                Debug.WriteLine("Served Collection from Cache.");
                return(cachedResult);
            }

            var client = new BoardGameGeekClient();

            IEnumerable <CollectionItem> games = (await client.LoadCollection(username)).ToList();
            var gamesById = games.ToLookup(g => g.GameId);

            if (grouped || details)
            {
                foreach (var game in games)
                {
                    if (game.UserComment.Contains("%Expands:"))
                    {
                        game.IsExpansion = true;
                    }
                }
                var expansions = from g in games where g.IsExpansion == true orderby g.Name select g;;

                HashSet <int> gameIds = new HashSet <int>();
                if (details)
                {
                    // get details for everything
                    gameIds.UnionWith(games.Select(g => g.GameId));
                }
                else if (grouped)
                {
                    // get details only for expansions
                    gameIds.UnionWith(expansions.Select(g => g.GameId));
                }

                var gameDetailsList = await client.ParallelLoadGames(gameIds);

                var gameDetailsById = gameDetailsList.Where(g => g != null).ToDictionary(g => g.GameId);

                if (details)
                {
                    foreach (var game in games)
                    {
                        if (gameDetailsById.ContainsKey(game.GameId))
                        {
                            var gameDetails = gameDetailsById[game.GameId];
                            game.Description = gameDetails.Description;
                            game.Mechanics   = gameDetails.Mechanics;
                            game.BGGRating   = gameDetails.BGGRating;
                            game.Artists     = gameDetails.Artists;
                            game.Publishers  = gameDetails.Publishers;
                            game.Designers   = gameDetails.Designers;
                        }
                    }
                }

                if (grouped)
                {
                    Regex expansionCommentExpression = new Regex(@"%Expands:(.*\w+.*)\[(\d+)\]", RegexOptions.Compiled);
                    foreach (var expansion in expansions)
                    {
                        if (gameDetailsById.ContainsKey(expansion.GameId))
                        {
                            var expansionDetails = gameDetailsById[expansion.GameId];
                            if (expansionDetails != null)
                            {
                                var expandsLinks = new List <BoardGameLink>(expansionDetails.Expands ?? new List <BoardGameLink>());
                                if (expansion.UserComment.Contains("%Expands:"))
                                {
                                    var match = expansionCommentExpression.Match(expansion.UserComment);
                                    if (match.Success)
                                    {
                                        var name = match.Groups[1].Value.Trim();
                                        var id   = int.Parse(match.Groups[2].Value.Trim());
                                        expandsLinks.Add(new BoardGameLink
                                        {
                                            GameId = id,
                                            Name   = name
                                        });
                                        expansion.UserComment = expansionCommentExpression.Replace(expansion.UserComment, "").Trim();
                                    }
                                }
                                foreach (var link in expandsLinks)
                                {
                                    var specificGames = gamesById[link.GameId];
                                    foreach (var game in specificGames)
                                    {
                                        if (game.IsExpansion)
                                        {
                                            continue;
                                        }

                                        if (game.Expansions == null)
                                        {
                                            game.Expansions = new List <CollectionItem>();
                                        }
                                        game.Expansions.Add(expansion.Clone());
                                    }
                                }
                            }
                        }
                    }

                    // filter expansions out of the top level result set
                    games = games.Where(g => !g.IsExpansion);
                }
            }

            Regex removeArticles = new Regex(@"^the\ |a\ |an\ ");

            games = games.OrderBy(g => removeArticles.Replace(g.Name.ToLower(), ""));

            var result = games.ToList();

            //Cache.Default.Set(Cache.CollectionKey(username, grouped, details), result, DateTimeOffset.Now.AddSeconds(15));

            return(result);
        }
コード例 #12
0
        public async Task<Challenge> Get(int id)
        {
            var cachedResult = Cache.Default.Get(Cache.ChallengeKey(id)) as Challenge;
            if (cachedResult != null)
            {
                Debug.WriteLine("Served Collection from Cache.");
                return cachedResult;
            }

            var client = new BoardGameGeekClient();
            var geeklist = await client.LoadGeekList(id);

            var options = ParseOptions(geeklist.Description);
            var goalPerGame = options.ContainsKey("GoalPerGame") ? int.Parse((string)options["GoalPerGame"]) : -1;
            var start = options.ContainsKey("Start") ? DateTime.Parse((string)options["Start"]) : (DateTime?)null;
            var end = options.ContainsKey("End") ? DateTime.Parse((string)options["End"]) : (DateTime?)null;

            var pageOne = await client.LoadPlays(geeklist.Username, 1, start, end);
            var plays = new List<PlayItem>();

            List<Task<Plays>> tasks = new List<Task<Plays>>();
            plays.AddRange(pageOne.Items);
            if (pageOne.Total > 100)
            {
                int remaining = pageOne.Total - 100;
                int page = 2;

                while (remaining > 0)
                {
                    tasks.Add(client.LoadPlays(geeklist.Username, page, start, end));
                    page++;
                    remaining -= 100;
                }
            }

            Challenge challenge = new Challenge()
            {
                GeekListId = id,
                Title = geeklist.Title,
                Username = geeklist.Username,
                Start = start != null ? start.Value.ToString("yyyy-MM-dd") : null,
                End = start != null ? end.Value.ToString("yyyy-MM-dd") : null,
                GoalPerGame = goalPerGame,
                Items = new List<ChallengeItem>()
            };

            var games = from item in geeklist.Items
                        select new
                        {
                            GameId = item.GameId,
                            Name = item.Name,
                            Description = item.Description
                        };


            var itemsById = new Dictionary<int, ChallengeItem>();

            foreach (var item in games)
            {
                var gameOptions = ParseOptions(item.Description);
                var challengeItem = new ChallengeItem()
                {
                    GameId = item.GameId,
                    Name = item.Name
                };

                if (gameOptions.ContainsKey("AltName"))
                {
                    challengeItem.Name = (string)gameOptions["AltName"];
                }
                if (gameOptions.ContainsKey("AdditionalGameId"))
                {
                    challengeItem.AdditionalGameIds = new List<int>();
                    var list = gameOptions["AdditionalGameId"] as List<string>;
                    if (list == null)
                    {
                        challengeItem.AdditionalGameIds.Add(int.Parse((string)gameOptions["AdditionalGameId"]));
                    }
                    else
                    {
                        challengeItem.AdditionalGameIds.AddRange(list.Select(i => int.Parse((string)i)));
                    }
                }
                
                challenge.Items.Add(challengeItem);
                itemsById[item.GameId] = challengeItem;
                if (challengeItem.AdditionalGameIds != null)
                {
                    foreach (var altGameId in challengeItem.AdditionalGameIds)
                    {
                        itemsById[altGameId] = challengeItem;
                    }
                }
            }

            var additionalPages = await Task.WhenAll(tasks);
            foreach (var page in additionalPages)
            {
                plays.AddRange(page.Items);
            }

            foreach (var play in plays)
            {
                if (itemsById.ContainsKey(play.GameId))
                {
                    itemsById[play.GameId].PlayCount++;
                }
            }

            challenge.Complete = true;
            foreach (var game in challenge.Items)
            {
                if (game.PlayCount >= goalPerGame)
                {
                    game.Complete = true;
                }
                else
                {
                    challenge.Complete = false;
                }
            }

            Cache.Default.Set(Cache.ChallengeKey(id), challenge, DateTimeOffset.Now.AddSeconds(15));
            return challenge;

        }
コード例 #13
0
ファイル: ChallengeController.cs プロジェクト: n0th/ludusBBG
        public async Task <Challenge> Get(int id)
        {
            var cachedResult = Cache.Default.Get(Cache.ChallengeKey(id)) as Challenge;

            if (cachedResult != null)
            {
                Debug.WriteLine("Served Collection from Cache.");
                return(cachedResult);
            }

            var client   = new BoardGameGeekClient();
            var geeklist = await client.LoadGeekList(id);

            var options     = ParseOptions(geeklist.Description);
            var goalPerGame = options.ContainsKey("GoalPerGame") ? int.Parse((string)options["GoalPerGame"]) : -1;
            var start       = options.ContainsKey("Start") ? DateTime.Parse((string)options["Start"]) : (DateTime?)null;
            var end         = options.ContainsKey("End") ? DateTime.Parse((string)options["End"]) : (DateTime?)null;

            var pageOne = await client.LoadPlays(geeklist.Username, 1, start, end);

            var plays = new List <PlayItem>();

            List <Task <Plays> > tasks = new List <Task <Plays> >();

            plays.AddRange(pageOne.Items);
            if (pageOne.Total > 100)
            {
                int remaining = pageOne.Total - 100;
                int page      = 2;

                while (remaining > 0)
                {
                    tasks.Add(client.LoadPlays(geeklist.Username, page, start, end));
                    page++;
                    remaining -= 100;
                }
            }

            Challenge challenge = new Challenge()
            {
                GeekListId                = id,
                Title                     = geeklist.Title,
                Username                  = geeklist.Username,
                Start                     = start != null?start.Value.ToString("yyyy-MM-dd") : null,
                                      End = start != null?end.Value.ToString("yyyy-MM-dd") : null,
                                                GoalPerGame = goalPerGame,
                                                Items       = new List <ChallengeItem>()
            };

            var games = from item in geeklist.Items
                        select new
            {
                GameId      = item.GameId,
                Name        = item.Name,
                Description = item.Description
            };


            var itemsById = new Dictionary <int, ChallengeItem>();

            foreach (var item in games)
            {
                var gameOptions   = ParseOptions(item.Description);
                var challengeItem = new ChallengeItem()
                {
                    GameId = item.GameId,
                    Name   = item.Name
                };

                if (gameOptions.ContainsKey("AltName"))
                {
                    challengeItem.Name = (string)gameOptions["AltName"];
                }
                if (gameOptions.ContainsKey("AdditionalGameId"))
                {
                    challengeItem.AdditionalGameIds = new List <int>();
                    var list = gameOptions["AdditionalGameId"] as List <string>;
                    if (list == null)
                    {
                        challengeItem.AdditionalGameIds.Add(int.Parse((string)gameOptions["AdditionalGameId"]));
                    }
                    else
                    {
                        challengeItem.AdditionalGameIds.AddRange(list.Select(i => int.Parse((string)i)));
                    }
                }

                challenge.Items.Add(challengeItem);
                itemsById[item.GameId] = challengeItem;
                if (challengeItem.AdditionalGameIds != null)
                {
                    foreach (var altGameId in challengeItem.AdditionalGameIds)
                    {
                        itemsById[altGameId] = challengeItem;
                    }
                }
            }

            var additionalPages = await Task.WhenAll(tasks);

            foreach (var page in additionalPages)
            {
                plays.AddRange(page.Items);
            }

            foreach (var play in plays)
            {
                if (itemsById.ContainsKey(play.GameId))
                {
                    itemsById[play.GameId].PlayCount++;
                }
            }

            challenge.Complete = true;
            foreach (var game in challenge.Items)
            {
                if (game.PlayCount >= goalPerGame)
                {
                    game.Complete = true;
                }
                else
                {
                    challenge.Complete = false;
                }
            }

            Cache.Default.Set(Cache.ChallengeKey(id), challenge, DateTimeOffset.Now.AddSeconds(15));
            return(challenge);
        }
コード例 #14
0
        public async Task<List<CollectionItem>> Get(string username, bool grouped = false, bool details = false)
        {
            var local = Request.IsLocal();
            var cachedResult = Cache.Default.Get(Cache.CollectionKey(username, grouped, details)) as List<CollectionItem>;
            if (cachedResult != null && !local)
            {
                Debug.WriteLine("Served Collection from Cache.");
                return cachedResult;
            }

            var client = new BoardGameGeekClient();

            IEnumerable<CollectionItem> games = (await client.LoadCollection(username)).ToList();
            var gamesById = games.ToLookup(g => g.GameId);

            if (grouped || details)
            {
                foreach (var game in games)
                {
                    if (game.UserComment.Contains("%Expands:"))
                    {
                        game.IsExpansion = true;
                    }
                }
                var expansions = from g in games where g.IsExpansion == true orderby g.Name select g; ;

                HashSet<int> gameIds = new HashSet<int>();
                if (details)
                {
                    // get details for everything
                    gameIds.UnionWith(games.Select(g => g.GameId));
                }
                else if (grouped)
                {
                    // get details only for expansions
                    gameIds.UnionWith(expansions.Select(g => g.GameId));
                }

                var gameDetailsList = await client.ParallelLoadGames(gameIds);
                var gameDetailsById = gameDetailsList.Where(g => g != null).ToDictionary(g => g.GameId);

                if (details)
                {
                    foreach (var game in games)
                    {
                        if (gameDetailsById.ContainsKey(game.GameId))
                        {
                            var gameDetails = gameDetailsById[game.GameId];
                            game.Description = gameDetails.Description;
                            game.Mechanics = gameDetails.Mechanics;
                            game.BGGRating = gameDetails.BGGRating;
                            game.Artists = gameDetails.Artists;
                            game.Publishers = gameDetails.Publishers;
                            game.Designers = gameDetails.Designers;
                        }
                    }
                }

                if (grouped)
                {
                    Regex expansionCommentExpression = new Regex(@"%Expands:(.*\w+.*)\[(\d+)\]", RegexOptions.Compiled);
                    foreach (var expansion in expansions)
                    {
                        if (gameDetailsById.ContainsKey(expansion.GameId))
                        {
                            var expansionDetails = gameDetailsById[expansion.GameId];
                            if (expansionDetails != null)
                            {
                                var expandsLinks = new List<BoardGameLink>(expansionDetails.Expands ?? new List<BoardGameLink>());
                                if (expansion.UserComment.Contains("%Expands:"))
                                {
                                    var match = expansionCommentExpression.Match(expansion.UserComment);
                                    if (match.Success)
                                    {
                                        var name = match.Groups[1].Value.Trim();
                                        var id = int.Parse(match.Groups[2].Value.Trim());
                                        expandsLinks.Add(new BoardGameLink
                                        {
                                            GameId = id,
                                            Name = name
                                        });
                                        expansion.UserComment = expansionCommentExpression.Replace(expansion.UserComment, "").Trim();
                                    }
                                }
                                foreach (var link in expandsLinks)
                                {
                                    var specificGames = gamesById[link.GameId];
                                    foreach (var game in specificGames)
                                    {
                                        if (game.IsExpansion)
                                        {
                                            continue;
                                        }

                                        if (game.Expansions == null)
                                        {
                                            game.Expansions = new List<CollectionItem>();
                                        }
                                        game.Expansions.Add(expansion.Clone());
                                    }
                                }
                            }
                        }
                    }

                    // filter expansions out of the top level result set
                    games = games.Where(g => !g.IsExpansion);
                }
            }

            Regex removeArticles = new Regex(@"^the\ |a\ |an\ ");

            games = games.OrderBy(g => removeArticles.Replace(g.Name.ToLower(), ""));

            var result = games.ToList();
            Cache.Default.Set(Cache.CollectionKey(username, grouped, details), result, DateTimeOffset.Now.AddSeconds(15));

            return result;
        }