diff --git a/GiantBomb.Api.Tests/Search.cs b/GiantBomb.Api.Tests/Search.cs
index ef41fa8..c9847ea 100644
--- a/GiantBomb.Api.Tests/Search.cs
+++ b/GiantBomb.Api.Tests/Search.cs
@@ -13,6 +13,9 @@ public void search_resource_should_return_one_result_for_skyrim() {
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Count);
Assert.AreEqual(33394, result.First().Id);
+ Assert.IsNotNull(result.First().Platforms);
+ Assert.IsNotNull(result.First().Platforms.First());
+ Assert.IsTrue(result.First().Platforms.First().Id > 0, "Platform is invalid");
}
[Test]
@@ -58,5 +61,16 @@ public void search_resource_should_limit_fields_to_id_for_all_result() {
Assert.IsTrue(result.All(g => g.Id > 0));
Assert.IsTrue(result.All(g => string.IsNullOrWhiteSpace(g.Name)));
}
+
+ ///
+ /// BUGFIX: "mario" returns dup resultset
+ ///
+ [Test]
+ public void search_resource_should_not_return_duplicates_for_mario() {
+ var result = _client.SearchForAllGames("mario", limitFields: new[] { "id" }).ToList();
+
+ Assert.IsNotNull(result);
+ Assert.IsFalse(result.Any(r => result.Count(r2 => r2.Id == r.Id) > 1), "Results contain duplicate IDs");
+ }
}
}
diff --git a/GiantBomb.Api/Core.cs b/GiantBomb.Api/Core.cs
index d69b77a..c39d543 100644
--- a/GiantBomb.Api/Core.cs
+++ b/GiantBomb.Api/Core.cs
@@ -1,161 +1,168 @@
-using System;
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Reflection;
-using System.Text;
-using GiantBomb.Api.Model;
-using RestSharp;
-
-namespace GiantBomb.Api {
- public partial class GiantBombRestClient : IGiantBombRestClient {
- private readonly RestClient _client;
-
- ///
- /// Base URL of API (defaults to http://www.giantbomb.com/api/)
- ///
- public string BaseUrl { get; set; }
-
- ///
- /// Your GiantBomb API token
- ///
- private string ApiKey { get; set; }
-
- ///
- /// Create a new Rest client with your API token and custom base URL
- ///
- /// Your secret API token
- /// The base API URL, for example, pre-release API versions
- public GiantBombRestClient(string apiToken, Uri baseUrl) {
- BaseUrl = baseUrl.ToString();
- ApiKey = apiToken;
-
- var assembly = Assembly.GetExecutingAssembly();
- var version = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion;
-
- _client = new RestClient
- {
- UserAgent = "giantbomb-csharp/" + version,
- BaseUrl = BaseUrl
- };
-
- // API token is used on every request
- _client.AddDefaultParameter("api_key", ApiKey);
- _client.AddDefaultParameter("format", "json");
-
- // Deserializer
- _client.AddHandler("application/text+json", new FastJsonDeserializer());
- }
-
- public GiantBombRestClient(string apiToken)
- : this(apiToken, new Uri("http://www.giantbomb.com/api/")) {
-
- }
-
-#if FRAMEWORK
- ///
- /// Execute a manual REST request
- ///
- /// The type of object to create and populate with the returned data.
- /// The RestRequest to execute (will use client credentials)
- public T Execute(RestRequest request) where T : new() {
- var response = _client.Execute(request);
- return response.Data;
- }
-
- ///
- /// Execute a manual REST request
- ///
- /// The RestRequest to execute (will use client credentials)
- public IRestResponse Execute(RestRequest request) {
- return _client.Execute(request);
- }
-#endif
-
- public virtual RestRequest GetListResource(string resource, int page = 1, int pageSize = GiantBombBase.DefaultLimit, string[] fieldList = null, IDictionary sortOptions = null, IDictionary filterOptions = null) {
- if (pageSize > GiantBombBase.DefaultLimit)
- throw new ArgumentOutOfRangeException("pageSize", "Page size cannot be greater than " + GiantBombBase.DefaultLimit + ".");
-
- var request = new RestRequest {
- Resource = resource + "//",
- DateFormat = "yyyy-MM-dd HH:mm:ss"
- };
-
- if (page > 1) {
- request.AddParameter("offset", pageSize * (page - 1));
- }
-
- request.AddParameter("limit", pageSize);
-
- if (fieldList != null)
- request.AddParameter("field_list", String.Join(",", fieldList));
-
- if (sortOptions != null)
- request.AddParameter("sort", BuildKeyValueListForUrl(sortOptions));
-
- if (filterOptions != null)
- request.AddParameter("filter", BuildKeyValueListForUrl(filterOptions));
-
- return request;
- }
-
- private string BuildKeyValueListForUrl(IEnumerable> dictionary)
- {
- // format is like :,:
- return String.Join(",", (from pair in dictionary
- select pair.Key + ":" + pair.Value).ToArray());
- }
-
- private string BuildKeyValueListForUrl(IEnumerable> sortOptions)
- {
-
- var sortDictionary = new Dictionary();
-
- foreach(var kv in sortOptions)
- sortDictionary.Add(kv.Key, kv.Value == SortDirection.Ascending ? "asc" : "desc");
-
- return BuildKeyValueListForUrl(sortDictionary);
- }
-
- public virtual IEnumerable GetListResource(string resource, int page = 1, int pageSize = GiantBombBase.DefaultLimit, string[] fieldList = null, IDictionary sortOptions = null, IDictionary filterOptions = null) where TResult : new() {
- var request = GetListResource(resource, page, pageSize, fieldList, sortOptions, filterOptions);
- var results = Execute>(request);
-
- if (results != null && results.StatusCode == GiantBombBase.StatusOk)
- return results.Results;
-
- return null;
- }
-
- public virtual RestRequest GetSingleResource(string resource, int resourceId, int id, string[] fieldList = null) {
- var request = new RestRequest {
- Resource = resource + "/{ResourceId}-{Id}//",
- DateFormat = "yyyy-MM-dd HH:mm:ss"
- };
-
- request.AddUrlSegment("ResourceId", resourceId.ToString(CultureInfo.InvariantCulture));
- request.AddUrlSegment("Id", id.ToString(CultureInfo.InvariantCulture));
-
- if (fieldList != null)
- request.AddParameter("field_list", String.Join(",", fieldList));
-
- return request;
- }
-
- public virtual TResult GetSingleResource(string resource, int resourceId, int id, string[] fieldList = null) where TResult : class, new() {
- var request = GetSingleResource(resource, resourceId, id, fieldList);
- var result = Execute>(request);
-
- if (result != null && result.StatusCode == GiantBombBase.StatusOk)
- return result.Results;
-
- return null;
- }
- }
-
- public enum SortDirection
- {
- Ascending,
- Descending
- }
-}
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using GiantBomb.Api.Model;
+using RestSharp;
+
+namespace GiantBomb.Api {
+ public partial class GiantBombRestClient : IGiantBombRestClient {
+ private readonly RestClient _client;
+
+ ///
+ /// Base URL of API (defaults to http://www.giantbomb.com/api/)
+ ///
+ public string BaseUrl { get; set; }
+
+ ///
+ /// Your GiantBomb API token
+ ///
+ private string ApiKey { get; set; }
+
+ ///
+ /// Create a new Rest client with your API token and custom base URL
+ ///
+ /// Your secret API token
+ /// The base API URL, for example, pre-release API versions
+ public GiantBombRestClient(string apiToken, Uri baseUrl) {
+ BaseUrl = baseUrl.ToString();
+ ApiKey = apiToken;
+
+ var assembly = Assembly.GetExecutingAssembly();
+ var version = System.Diagnostics.FileVersionInfo.GetVersionInfo(assembly.Location).ProductVersion;
+
+ _client = new RestClient
+ {
+ UserAgent = "giantbomb-csharp/" + version,
+ BaseUrl = BaseUrl
+ };
+
+ // API token is used on every request
+ _client.AddDefaultParameter("api_key", ApiKey);
+ _client.AddDefaultParameter("format", "json");
+
+ // Deserializer
+ _client.AddHandler("application/text+json", new FastJsonDeserializer());
+ }
+
+ public GiantBombRestClient(string apiToken)
+ : this(apiToken, new Uri("http://www.giantbomb.com/api/")) {
+
+ }
+
+#if FRAMEWORK
+ ///
+ /// Execute a manual REST request
+ ///
+ /// The type of object to create and populate with the returned data.
+ /// The RestRequest to execute (will use client credentials)
+ public T Execute(RestRequest request) where T : new() {
+ var response = _client.Execute(request);
+ return response.Data;
+ }
+
+ ///
+ /// Execute a manual REST request
+ ///
+ /// The RestRequest to execute (will use client credentials)
+ public IRestResponse Execute(RestRequest request) {
+ return _client.Execute(request);
+ }
+#endif
+
+ public virtual RestRequest GetListResource(string resource, int page = 1, int pageSize = GiantBombBase.DefaultLimit, string[] fieldList = null, IDictionary sortOptions = null, IDictionary filterOptions = null) {
+ if (pageSize > GiantBombBase.DefaultLimit)
+ throw new ArgumentOutOfRangeException("pageSize", "Page size cannot be greater than " + GiantBombBase.DefaultLimit + ".");
+
+ var request = new RestRequest {
+ Resource = resource + "//",
+ DateFormat = "yyyy-MM-dd HH:mm:ss"
+ };
+
+ if (page > 1) {
+
+ // HACK: Giant Bomb uses `page` for search instead of `offset`, assholes
+ if (resource == "search") {
+ request.AddParameter("page", page);
+ }
+ else {
+ request.AddParameter("offset", pageSize*(page - 1));
+ }
+ }
+
+ request.AddParameter("limit", pageSize);
+
+ if (fieldList != null)
+ request.AddParameter("field_list", String.Join(",", fieldList));
+
+ if (sortOptions != null)
+ request.AddParameter("sort", BuildKeyValueListForUrl(sortOptions));
+
+ if (filterOptions != null)
+ request.AddParameter("filter", BuildKeyValueListForUrl(filterOptions));
+
+ return request;
+ }
+
+ private string BuildKeyValueListForUrl(IEnumerable> dictionary)
+ {
+ // format is like :,:
+ return String.Join(",", (from pair in dictionary
+ select pair.Key + ":" + pair.Value).ToArray());
+ }
+
+ private string BuildKeyValueListForUrl(IEnumerable> sortOptions)
+ {
+
+ var sortDictionary = new Dictionary();
+
+ foreach(var kv in sortOptions)
+ sortDictionary.Add(kv.Key, kv.Value == SortDirection.Ascending ? "asc" : "desc");
+
+ return BuildKeyValueListForUrl(sortDictionary);
+ }
+
+ public virtual IEnumerable GetListResource(string resource, int page = 1, int pageSize = GiantBombBase.DefaultLimit, string[] fieldList = null, IDictionary sortOptions = null, IDictionary filterOptions = null) where TResult : new() {
+ var request = GetListResource(resource, page, pageSize, fieldList, sortOptions, filterOptions);
+ var results = Execute>(request);
+
+ if (results != null && results.StatusCode == GiantBombBase.StatusOk)
+ return results.Results;
+
+ return null;
+ }
+
+ public virtual RestRequest GetSingleResource(string resource, int resourceId, int id, string[] fieldList = null) {
+ var request = new RestRequest {
+ Resource = resource + "/{ResourceId}-{Id}//",
+ DateFormat = "yyyy-MM-dd HH:mm:ss"
+ };
+
+ request.AddUrlSegment("ResourceId", resourceId.ToString(CultureInfo.InvariantCulture));
+ request.AddUrlSegment("Id", id.ToString(CultureInfo.InvariantCulture));
+
+ if (fieldList != null)
+ request.AddParameter("field_list", String.Join(",", fieldList));
+
+ return request;
+ }
+
+ public virtual TResult GetSingleResource(string resource, int resourceId, int id, string[] fieldList = null) where TResult : class, new() {
+ var request = GetSingleResource(resource, resourceId, id, fieldList);
+ var result = Execute>(request);
+
+ if (result != null && result.StatusCode == GiantBombBase.StatusOk)
+ return result.Results;
+
+ return null;
+ }
+ }
+
+ public enum SortDirection
+ {
+ Ascending,
+ Descending
+ }
+}
diff --git a/GiantBomb.Api/Properties/AssemblyInfo.cs b/GiantBomb.Api/Properties/AssemblyInfo.cs
index 102b34e..d86d5a1 100644
--- a/GiantBomb.Api/Properties/AssemblyInfo.cs
+++ b/GiantBomb.Api/Properties/AssemblyInfo.cs
@@ -1,17 +1,17 @@
-using System.Reflection;
-using System.Runtime.InteropServices;
-
-[assembly: AssemblyTitle("GiantBomb.Api")]
-[assembly: AssemblyDescription("RestSharp-based API wrapper for the GiantBomb games database public API")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("kayub")]
-[assembly: AssemblyProduct("GiantBomb.Api")]
-[assembly: AssemblyCopyright("Copyright © kayub 2012")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-[assembly: ComVisible(false)]
-[assembly: Guid("5d910805-e5ae-48f0-8e39-d37104d7a91e")]
-
-[assembly: AssemblyVersion("2.0.2")]
-[assembly: AssemblyFileVersion("2.0.2")]
-[assembly: AssemblyInformationalVersion("2.0.2")]
\ No newline at end of file
+using System.Reflection;
+using System.Runtime.InteropServices;
+
+[assembly: AssemblyTitle("GiantBomb.Api")]
+[assembly: AssemblyDescription("RestSharp-based API wrapper for the GiantBomb games database public API")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("kayub")]
+[assembly: AssemblyProduct("GiantBomb.Api")]
+[assembly: AssemblyCopyright("Copyright © kayub 2012")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+[assembly: ComVisible(false)]
+[assembly: Guid("5d910805-e5ae-48f0-8e39-d37104d7a91e")]
+
+[assembly: AssemblyVersion("2.0.3")]
+[assembly: AssemblyFileVersion("2.0.3")]
+[assembly: AssemblyInformationalVersion("2.0.3")]
\ No newline at end of file
diff --git a/GiantBomb.Api/readme.txt b/GiantBomb.Api/readme.txt
index 81ae1f3..93f5b4a 100644
--- a/GiantBomb.Api/readme.txt
+++ b/GiantBomb.Api/readme.txt
@@ -1,54 +1,54 @@
-GiantBomb C#
-------------
-
-## API v2 Support
-
-GiantBomb-C# 2.0+ is only compatible with GiantBomb APIv2. Keep using the old packages if you need v1 support, as there are breaking changes in v2!
-
-### Notable Changes
-
-* Search is now MUCH better in GiantBomb's v2 API, there's little to no need for `SearchAllGames` now unless you expect/want more than 100 results
-* List resources support new `sort` and `filter` options
-* All single resource requests must use a resource ID, e.g. "game/3030-33394", which GBCS implements transparently for you
-* `Game` and `Release` now have `ExpectedReleaseDay`
-* Search results now include platforms
-* `Game` has two new fields:
- - `Aliases` - newline delimited aliases
- - `OriginalGameRating`
-* You can now use `GetReleasesForGame()` to directly retrieve releases for a game in one request
-* Using [FastJSON](http://www.codeproject.com/Articles/159450/fastJSON) to deserialize, which is... fast, obviously
-
-### Breaking Changes/Known Issues
-
-* Platform/Release `DateLastUpdated` is spelled incorrectly on the API (#10)
-
-## Readme
-
-This library aims to wrap the GiantBomb REST API in C# with strongly-typed models and is built on top of [RestSharp](https://github.com/johnsheehan/RestSharp).
-
-It also helps make your life easier when dealing with searching because it recursively fetches your search results all at once to enable better sorting.
-
-**Note: This is not really needed anymore due to search improvements in APIv2**
-
- var giantBomb = new GiantBombRestClient();
-
- // Get all search results
- var results = giantBomb.SearchForAllGames("assassin's creed");
-
- // Display
- return results.OrderByDescending(g => g.DateAdded)
-
-
-## Nuget
-Download and install the GiantBomb.Api Nuget package:
-
- PM> Install-Package GiantBomb.Api
-
-## Contributing
-Read about [contributing on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki). If you plan to contribute, you **must** read this.
-
-## Examples
-Read about [examples on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki).
-
-## License
-Dual-licensed on MIT & GPL
\ No newline at end of file
+GiantBomb C#
+------------
+
+## 2.0.3
+
+- Fixes issue with search paging (`offset` vs. `page` parameter)
+
+## API v2 Support
+
+GiantBomb-C# 2.0+ is only compatible with GiantBomb APIv2. Keep using the old packages if you need v1 support, as there are breaking changes in v2!
+
+### Notable Changes
+
+* Search is now MUCH better in GiantBomb's v2 API, there's little to no need for `SearchAllGames` now unless you expect/want more than 100 results
+* List resources support new `sort` and `filter` options
+* All single resource requests must use a resource ID, e.g. "game/3030-33394", which GBCS implements transparently for you
+* `Game` and `Release` now have `ExpectedReleaseDay`
+* Search results now include platforms
+* `Game` has two new fields:
+ - `Aliases` - newline delimited aliases
+ - `OriginalGameRating`
+* You can now use `GetReleasesForGame()` to directly retrieve releases for a game in one request
+* Using [FastJSON](http://www.codeproject.com/Articles/159450/fastJSON) to deserialize, which is... fast, obviously
+
+## Readme
+
+This library aims to wrap the GiantBomb REST API in C# with strongly-typed models and is built on top of [RestSharp](https://github.com/johnsheehan/RestSharp).
+
+It also helps make your life easier when dealing with searching because it recursively fetches your search results all at once to enable better sorting.
+
+**Note: This is not really needed anymore due to search improvements in APIv2**
+
+ var giantBomb = new GiantBombRestClient();
+
+ // Get all search results
+ var results = giantBomb.SearchForAllGames("assassin's creed");
+
+ // Display
+ return results.OrderByDescending(g => g.DateAdded)
+
+
+## Nuget
+Download and install the GiantBomb.Api Nuget package:
+
+ PM> Install-Package GiantBomb.Api
+
+## Contributing
+Read about [contributing on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki). If you plan to contribute, you **must** read this.
+
+## Examples
+Read about [examples on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki).
+
+## License
+Dual-licensed on MIT & GPL
diff --git a/Readme.md b/Readme.md
index 0e9bc82..93f5b4a 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,50 +1,54 @@
-GiantBomb C#
-------------
-
-## API v2 Support
-
-GiantBomb-C# 2.0+ is only compatible with GiantBomb APIv2. Keep using the old packages if you need v1 support, as there are breaking changes in v2!
-
-### Notable Changes
-
-* Search is now MUCH better in GiantBomb's v2 API, there's little to no need for `SearchAllGames` now unless you expect/want more than 100 results
-* List resources support new `sort` and `filter` options
-* All single resource requests must use a resource ID, e.g. "game/3030-33394", which GBCS implements transparently for you
-* `Game` and `Release` now have `ExpectedReleaseDay`
-* Search results now include platforms
-* `Game` has two new fields:
- - `Aliases` - newline delimited aliases
- - `OriginalGameRating`
-* You can now use `GetReleasesForGame()` to directly retrieve releases for a game in one request
-* Using [FastJSON](http://www.codeproject.com/Articles/159450/fastJSON) to deserialize, which is... fast, obviously
-
-## Readme
-
-This library aims to wrap the GiantBomb REST API in C# with strongly-typed models and is built on top of [RestSharp](https://github.com/johnsheehan/RestSharp).
-
-It also helps make your life easier when dealing with searching because it recursively fetches your search results all at once to enable better sorting.
-
-**Note: This is not really needed anymore due to search improvements in APIv2**
-
- var giantBomb = new GiantBombRestClient();
-
- // Get all search results
- var results = giantBomb.SearchForAllGames("assassin's creed");
-
- // Display
- return results.OrderByDescending(g => g.DateAdded)
-
-
-## Nuget
-Download and install the GiantBomb.Api Nuget package:
-
- PM> Install-Package GiantBomb.Api
-
-## Contributing
-Read about [contributing on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki). If you plan to contribute, you **must** read this.
-
-## Examples
-Read about [examples on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki).
-
-## License
-Dual-licensed on MIT & GPL
+GiantBomb C#
+------------
+
+## 2.0.3
+
+- Fixes issue with search paging (`offset` vs. `page` parameter)
+
+## API v2 Support
+
+GiantBomb-C# 2.0+ is only compatible with GiantBomb APIv2. Keep using the old packages if you need v1 support, as there are breaking changes in v2!
+
+### Notable Changes
+
+* Search is now MUCH better in GiantBomb's v2 API, there's little to no need for `SearchAllGames` now unless you expect/want more than 100 results
+* List resources support new `sort` and `filter` options
+* All single resource requests must use a resource ID, e.g. "game/3030-33394", which GBCS implements transparently for you
+* `Game` and `Release` now have `ExpectedReleaseDay`
+* Search results now include platforms
+* `Game` has two new fields:
+ - `Aliases` - newline delimited aliases
+ - `OriginalGameRating`
+* You can now use `GetReleasesForGame()` to directly retrieve releases for a game in one request
+* Using [FastJSON](http://www.codeproject.com/Articles/159450/fastJSON) to deserialize, which is... fast, obviously
+
+## Readme
+
+This library aims to wrap the GiantBomb REST API in C# with strongly-typed models and is built on top of [RestSharp](https://github.com/johnsheehan/RestSharp).
+
+It also helps make your life easier when dealing with searching because it recursively fetches your search results all at once to enable better sorting.
+
+**Note: This is not really needed anymore due to search improvements in APIv2**
+
+ var giantBomb = new GiantBombRestClient();
+
+ // Get all search results
+ var results = giantBomb.SearchForAllGames("assassin's creed");
+
+ // Display
+ return results.OrderByDescending(g => g.DateAdded)
+
+
+## Nuget
+Download and install the GiantBomb.Api Nuget package:
+
+ PM> Install-Package GiantBomb.Api
+
+## Contributing
+Read about [contributing on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki). If you plan to contribute, you **must** read this.
+
+## Examples
+Read about [examples on the wiki](https://github.com/kamranayub/GiantBomb-CSharp/wiki).
+
+## License
+Dual-licensed on MIT & GPL