diff --git a/.gitignore b/.gitignore index f3df4a2..72f6958 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ obj/ *.suo *.pubxml packages/*/ -MalApi/package/ \ No newline at end of file +MalApi/package/ +.vs/ diff --git a/MalApi.Example/Program.cs b/MalApi.Example/Program.cs index 5ab08fd..ca071e9 100644 --- a/MalApi.Example/Program.cs +++ b/MalApi.Example/Program.cs @@ -1,8 +1,10 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; + namespace MalApi.Example { class Program @@ -24,7 +26,26 @@ static void Main(string[] args) api.UserAgent = "MalApiExample"; api.TimeoutInMs = 15000; - MalUserLookupResults userLookup = api.GetAnimeListForUser("LordHighCaptain"); + var animeUpdateInfo = new AnimeUpdate() + { + Episode = 26, + Status = AnimeCompletionStatus.Completed, + Score = 9, + }; + string userUpdateAnime = api.UpdateAnimeForUser(1, animeUpdateInfo, "user", "password"); + + var mangaUpdateInfo = new MangaUpdate() + { + Chapter = 20, + Volume = 3, + Score = 8, + Status = MangaCompletionStatus.Completed + }; + string userUpdateManga = api.UpdateMangaForUser(952, mangaUpdateInfo, "user", "password"); + + + + MalUserLookupResults userLookup = api.GetAnimeListForUser("user"); foreach (MyAnimeListEntry listEntry in userLookup.AnimeList) { Console.WriteLine("Rating for {0}: {1}", listEntry.AnimeInfo.Title, listEntry.Score); diff --git a/MalApi.IntegrationTests/GetMalDetails.cs b/MalApi.IntegrationTests/GetMalDetails.cs new file mode 100644 index 0000000..fdbcc0b --- /dev/null +++ b/MalApi.IntegrationTests/GetMalDetails.cs @@ -0,0 +1,149 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using HtmlAgilityPack; + +namespace MalApi.IntegrationTests +{ + class GetMalDetails : IDisposable + { + protected static readonly string NoRedirectMalUri = "https://myanimelist.net/pressroom"; + protected static readonly string LoginUri = "https://myanimelist.net/login.php"; + + private HttpClientHandler m_httpHandler; + private HttpClient m_httpClient; + + public string UserAgent { get; set; } + + private string CsrfToken { get; set; } + + /// + /// Timeout in milliseconds for requests to MAL. Defaults to 15000 (15s). + /// + public int TimeoutInMs + { + get { return m_httpClient.Timeout.Milliseconds; } + set { m_httpClient.Timeout = TimeSpan.FromMilliseconds(value); } + } + + public GetMalDetails() + { + m_httpHandler = new HttpClientHandler() + { + AllowAutoRedirect = true, + UseCookies = true, + + AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, + }; + m_httpClient = new HttpClient(m_httpHandler); + TimeoutInMs = 15 * 1000; + } + + protected HttpRequestMessage InitNewRequest(string uri, HttpMethod method) + { + HttpRequestMessage request = new HttpRequestMessage(method, uri); + + if (UserAgent != null) + { + request.Headers.TryAddWithoutValidation("User-Agent", UserAgent); + } + + return request; + } + + protected async Task ProcessRequestAsync(HttpRequestMessage request, Func processingFunc, string baseErrorMessage, CancellationToken cancellationToken) + { + try + { + // Need to read the entire content at once here because response.Content.ReadAsStringAsync doesn't support cancellation. + using (HttpResponseMessage response = await m_httpClient.SendAsync(request, HttpCompletionOption.ResponseContentRead, cancellationToken).ConfigureAwait(continueOnCapturedContext: false)) + { + if (response.StatusCode == HttpStatusCode.OK) + { + var responseBody = await response.Content.ReadAsStringAsync().ConfigureAwait(continueOnCapturedContext: false); + return processingFunc(responseBody); + } + throw new MalApiRequestException(string.Format("{0} Status code was {1}", baseErrorMessage, (int)response.StatusCode)); + } + } + catch (Exception ex) + { + // If we didn't read a response, then there was an error with the request/response that may be fixable with a retry. + throw new MalApiRequestException(string.Format("{0} {1}", baseErrorMessage, ex.Message), ex); + } + } + + protected string ParseCsrfTokenFromHtml(string html) + { + Match match = Regex.Match(html, @""); + if (match.Success) + { + CsrfToken = match.Groups[1].ToString(); + } + + return CsrfToken; + } + + // internal for unit testing + protected string WebFormLoginHandler(string responseBody) + { + if (responseBody.Contains("Your username or password is incorrect.")) + { + throw new MalApiRequestException("Failed to log in. Recheck credentials."); + } + + return null; + } + + public void Login(string username, string password) + { + Login(username, password, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); + } + + private async Task Login(string username, string password, CancellationToken cancellationToken) + { + string url = NoRedirectMalUri; + + HttpRequestMessage request = InitNewRequest(url, HttpMethod.Get); + + // Get CSRF token + await ProcessRequestAsync(request, ParseCsrfTokenFromHtml, + cancellationToken: cancellationToken, + baseErrorMessage: "Failed connecting to MAL") + .ConfigureAwait(continueOnCapturedContext: false); + + // Login through the web form + url = LoginUri; + request = InitNewRequest(url, HttpMethod.Post); + + var contentPairs = new List> + { + new KeyValuePair("user_name", username), + new KeyValuePair("password", password), + new KeyValuePair("cookie", "1"), + new KeyValuePair("sublogin", "Login"), + new KeyValuePair("submit", "1"), + new KeyValuePair("csrf_token", CsrfToken) + }; + var content = new FormUrlEncodedContent(contentPairs); + request.Content = content; + + await ProcessRequestAsync(request, WebFormLoginHandler, + cancellationToken: cancellationToken, + baseErrorMessage: string.Format("Failed to log in for user {0}.", username)) + .ConfigureAwait(continueOnCapturedContext: false); + } + + public void Dispose() + { + m_httpClient.Dispose(); + m_httpHandler.Dispose(); + } + } +} diff --git a/MalApi.IntegrationTests/GetMangaListForUserTest.cs b/MalApi.IntegrationTests/GetMangaListForUserTest.cs new file mode 100644 index 0000000..302442b --- /dev/null +++ b/MalApi.IntegrationTests/GetMangaListForUserTest.cs @@ -0,0 +1,57 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using MalApi; +using System.Threading.Tasks; +using Xunit; +using System.Threading; + +namespace MalApi.IntegrationTests +{ + public class GetMangaListForUserTest + { + [Fact] + public void GetMangaListForUser() + { + string username = "naps250"; + using (MyAnimeListApi api = new MyAnimeListApi()) + { + MalUserLookupResults userLookup = api.GetMangaListForUser(username); + + Assert.NotEmpty(userLookup.MangaList); + } + } + + [Fact] + public void GetMangaListForUserCanceled() + { + string username = "naps250"; + using (MyAnimeListApi api = new MyAnimeListApi()) + { + CancellationTokenSource tokenSource = new CancellationTokenSource(); + Task userLookupTask = api.GetMangaListForUserAsync(username, tokenSource.Token); + tokenSource.Cancel(); + Assert.Throws(() => userLookupTask.GetAwaiter().GetResult()); + } + } + + [Fact] + public void GetMangaListForNonexistentUserThrowsCorrectException() + { + using (MyAnimeListApi api = new MyAnimeListApi()) + { + Assert.Throws(() => api.GetMangaListForUser("oijsfjisfdjfsdojpfsdp")); + } + } + + [Fact] + public void GetMangaListForNonexistentUserThrowsCorrectExceptionAsync() + { + using (MyAnimeListApi api = new MyAnimeListApi()) + { + Assert.ThrowsAsync(() => api.GetMangaListForUserAsync("oijsfjisfdjfsdojpfsdp")); + } + } + } +} \ No newline at end of file diff --git a/MalApi.IntegrationTests/GetUserAnimeDetailsTest.cs b/MalApi.IntegrationTests/GetUserAnimeDetailsTest.cs new file mode 100644 index 0000000..310f9fc --- /dev/null +++ b/MalApi.IntegrationTests/GetUserAnimeDetailsTest.cs @@ -0,0 +1,208 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using HtmlAgilityPack; + +namespace MalApi.IntegrationTests +{ + class GetUserAnimeDetailsTest : GetMalDetails + { + private static readonly string UserAnimeDetailsUri = "https://myanimelist.net/editlist.php?type=anime&id={0}"; + + public AnimeUpdate GetUserAnimeDetailsAsync(string username, int animeId) + { + return GetUserAnimeDetailsAsync(username, animeId, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); + } + + public async Task GetUserAnimeDetailsAsync(string username, int animeId, CancellationToken cancellationToken) + { + string url = string.Format(UserAnimeDetailsUri, animeId); + HttpRequestMessage request = InitNewRequest(url, HttpMethod.Get); + + AnimeUpdate results = await ProcessRequestAsync(request, ScrapeUserAnimeDetailsFromHtml, + cancellationToken: cancellationToken, + baseErrorMessage: string.Format("Failed getting user anime details for user {0} anime ID {1}.", username, animeId)) + .ConfigureAwait(continueOnCapturedContext: false); + return results; + } + + // internal for unit testing + internal AnimeUpdate ScrapeUserAnimeDetailsFromHtml(string userAnimeDetailsHtml) + { + AnimeUpdate results = new AnimeUpdate(); + + var doc = new HtmlDocument(); + doc.LoadHtml(userAnimeDetailsHtml); + + // Episode + results.Episode = doc.GetElementbyId("add_anime_num_watched_episodes").GetAttributeValue("value", -1); + + // Status + var parentNode = doc.GetElementbyId("add_anime_status"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Status = (AnimeCompletionStatus)childNode.GetAttributeValue("value", -1); + break; + } + } + + // Enable rewatching + results.EnableRewatching = doc.GetElementbyId("add_anime_is_rewatching").Attributes["checked"] == null ? 0 : 1; + + // Score + parentNode = doc.GetElementbyId("add_anime_score"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Score = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Start date + int day = -1; + int month = -1; + int year = -1; + parentNode = doc.GetElementbyId("add_anime_start_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_start_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_start_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateStart = null; + } + else + { + results.DateStart = new DateTime(year, month, day); + } + + // Date finish + parentNode = doc.GetElementbyId("add_anime_finish_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_finish_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_finish_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateFinish = null; + } + else + { + results.DateFinish = new DateTime(year, month, day); + } + + // Tags + results.Tags = doc.GetElementbyId("add_anime_tags").InnerText; + + // Priority + parentNode = doc.GetElementbyId("add_anime_priority"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Priority = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Storage type + parentNode = doc.GetElementbyId("add_anime_storage_type"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.StorageType = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Storage value + results.StorageValue = doc.GetElementbyId("add_anime_storage_value").GetAttributeValue("value", -1); + + // Times rewatched + results.TimesRewatched = doc.GetElementbyId("add_anime_num_watched_times").GetAttributeValue("value", -1); + + // Rewatch value + parentNode = doc.GetElementbyId("add_anime_rewatch_value"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.RewatchValue = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Comments + results.Comments = doc.GetElementbyId("add_anime_comments").InnerText; + + // Enable discussion + parentNode = doc.GetElementbyId("add_anime_is_asked_to_discuss"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + // Because 'Enable discussion = 1' sent for update sets the first options of the dropdown, which corresponds to 0 and vice versa... + int temp = childNode.GetAttributeValue("value", -1); + temp = temp == 1 ? 0 : 1; + results.EnableDiscussion = temp; + break; + } + } + + return results; + } + } +} diff --git a/MalApi.IntegrationTests/GetUserMangaDetailsTest.cs b/MalApi.IntegrationTests/GetUserMangaDetailsTest.cs new file mode 100644 index 0000000..671bdba --- /dev/null +++ b/MalApi.IntegrationTests/GetUserMangaDetailsTest.cs @@ -0,0 +1,198 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using HtmlAgilityPack; + +namespace MalApi.IntegrationTests +{ + class GetUserMangaDetailsTest : GetMalDetails + { + private static readonly string UserMangaDetailsUri = "https://myanimelist.net/panel.php?go=editmanga&id={0}"; + + public MangaUpdate GetUserMangaDetailsAsync(string username, int mangaId) + { + return GetUserMangaDetailsAsync(username, mangaId, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); + } + + public async Task GetUserMangaDetailsAsync(string username, int mangaId, CancellationToken cancellationToken) + { + string url = string.Format(UserMangaDetailsUri, mangaId); + HttpRequestMessage request = InitNewRequest(url, HttpMethod.Get); + + MangaUpdate results = await ProcessRequestAsync(request, ScrapeUserMangaDetailsFromHtml, + cancellationToken: cancellationToken, + baseErrorMessage: string.Format("Failed getting user manga details for user {0} manga ID {1}.", username, mangaId)) + .ConfigureAwait(continueOnCapturedContext: false); + return results; + } + + // internal for unit testing + internal MangaUpdate ScrapeUserMangaDetailsFromHtml(string userMangaDetailsHtml) + { + MangaUpdate results = new MangaUpdate(); + + var doc = new HtmlDocument(); + doc.LoadHtml(userMangaDetailsHtml); + + // Chapter + results.Chapter = doc.GetElementbyId("add_manga_num_read_chapters").GetAttributeValue("value", -1); + + // Volume + results.Volume = doc.GetElementbyId("add_manga_num_read_volumes").GetAttributeValue("value", -1); + + // Status + var parentNode = doc.GetElementbyId("add_manga_status"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Status = (MangaCompletionStatus)childNode.GetAttributeValue("value", -1); + break; + } + } + + // Enable rereading + results.EnableRereading = doc.GetElementbyId("add_manga_is_rereading").Attributes["checked"] == null ? 0 : 1; + + // Score + parentNode = doc.GetElementbyId("add_manga_score"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Score = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Start date + int day = -1; + int month = -1; + int year = -1; + parentNode = doc.GetElementbyId("add_manga_start_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_start_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_start_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateStart = null; + } + else + { + results.DateStart = new DateTime(year, month, day); + } + + // Date finish + parentNode = doc.GetElementbyId("add_manga_finish_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_finish_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_finish_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateFinish = null; + } + else + { + results.DateFinish = new DateTime(year, month, day); + } + + // Tags + results.Tags = doc.GetElementbyId("add_manga_tags").InnerText; + + // Priority + parentNode = doc.GetElementbyId("add_manga_priority"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Priority = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Times reread + results.TimesReread = doc.GetElementbyId("add_manga_num_read_times").GetAttributeValue("value", -1); + + // Reread value + parentNode = doc.GetElementbyId("add_manga_reread_value"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.RereadValue = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Comments + results.Comments = doc.GetElementbyId("add_manga_comments").InnerText; + + // Enable discussion + parentNode = doc.GetElementbyId("add_manga_is_asked_to_discuss"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + // Because 'Enable discussion = 1' sent for update sets the first options of the dropdown, which corresponds to 0 and vice versa... + int temp = childNode.GetAttributeValue("value", -1); + temp = temp == 1 ? 0 : 1; + results.EnableDiscussion = temp; + break; + } + } + + return results; + } + + } +} diff --git a/MalApi.IntegrationTests/MalApi.IntegrationTests.csproj b/MalApi.IntegrationTests/MalApi.IntegrationTests.csproj index c127efd..c985f9a 100644 --- a/MalApi.IntegrationTests/MalApi.IntegrationTests.csproj +++ b/MalApi.IntegrationTests/MalApi.IntegrationTests.csproj @@ -11,10 +11,11 @@ + - - + + diff --git a/MalApi.IntegrationTests/UpdateUserAnimeTest.cs b/MalApi.IntegrationTests/UpdateUserAnimeTest.cs new file mode 100644 index 0000000..e3e5e0d --- /dev/null +++ b/MalApi.IntegrationTests/UpdateUserAnimeTest.cs @@ -0,0 +1,1090 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace MalApi.IntegrationTests +{ + public class UpdateUserAnimeTest + { + [Fact] + public void UpdateAnimeForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate baseInfo = new AnimeUpdate() + { + Episode = 1, + Status = AnimeCompletionStatus.Watching, + Score = 3, + StorageType = 3, // VHS + StorageValue = 1, + TimesRewatched = 1, + RewatchValue = 2, // low + DateStart = new DateTime(2017, 10, 1), + DateFinish = new DateTime(2017, 10, 5), + Priority = 0, // low + EnableDiscussion = 0, + EnableRewatching = 0, + Comments = "base comment,base comment 2", + Tags = "test base tag, test base tag 2" + }; + + AnimeUpdate updateInfo = new AnimeUpdate() + { + Episode = 26, + Status = AnimeCompletionStatus.Completed, + Score = 8, + StorageType = 5, // VHS + StorageValue = 123, + TimesRewatched = 2, + RewatchValue = 4, // high + DateStart = new DateTime(2017, 12, 10), + DateFinish = new DateTime(2017, 12, 15), + Priority = 1, // medium + EnableDiscussion = 1, + EnableRewatching = 1, + Comments = "test updated comment, test updated comment2", + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, baseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(baseInfo.Episode, baseReults.Episode); + Assert.Equal(baseInfo.Status, baseReults.Status); + Assert.Equal(baseInfo.Score, baseReults.Score); + Assert.Equal(baseInfo.StorageType, baseReults.StorageType); + Assert.Equal(baseInfo.StorageValue, baseReults.StorageValue); + Assert.Equal(baseInfo.TimesRewatched, baseReults.TimesRewatched); + Assert.Equal(baseInfo.RewatchValue, baseReults.RewatchValue); + Assert.Equal(baseInfo.DateStart, baseReults.DateStart); + Assert.Equal(baseInfo.DateFinish, baseReults.DateFinish); + Assert.Equal(baseInfo.Priority, baseReults.Priority); + Assert.Equal(baseInfo.EnableDiscussion, baseReults.EnableDiscussion); + Assert.Equal(baseInfo.EnableRewatching, baseReults.EnableRewatching); + Assert.Equal(baseInfo.Comments, baseReults.Comments); + Assert.Equal(baseInfo.Tags, baseReults.Tags); + + result = api.UpdateAnimeForUser(animeId, updateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(updateInfo.Episode, updatedResults.Episode); + Assert.Equal(updateInfo.Status, updatedResults.Status); + Assert.Equal(updateInfo.Score, updatedResults.Score); + Assert.Equal(updateInfo.StorageType, updatedResults.StorageType); + Assert.Equal(updateInfo.StorageValue, updatedResults.StorageValue); + Assert.Equal(updateInfo.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(updateInfo.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(updateInfo.DateStart, updatedResults.DateStart); + Assert.Equal(updateInfo.DateFinish, updatedResults.DateFinish); + Assert.Equal(updateInfo.Priority, updatedResults.Priority); + Assert.Equal(updateInfo.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(updateInfo.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(updateInfo.Comments, updatedResults.Comments); + Assert.Equal(updateInfo.Tags, updatedResults.Tags); + + // Assert all values have been changed + Assert.NotEqual(baseReults.Episode, updatedResults.Episode); + Assert.NotEqual(baseReults.Status, updatedResults.Status); + Assert.NotEqual(baseReults.Score, updatedResults.Score); + Assert.NotEqual(baseReults.StorageType, updatedResults.StorageType); + Assert.NotEqual(baseReults.StorageValue, updatedResults.StorageValue); + Assert.NotEqual(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.NotEqual(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.NotEqual(baseReults.DateStart, updatedResults.DateStart); + Assert.NotEqual(baseReults.DateFinish, updatedResults.DateFinish); + Assert.NotEqual(baseReults.Priority, updatedResults.Priority); + Assert.NotEqual(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.NotEqual(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.NotEqual(baseReults.Comments, updatedResults.Comments); + Assert.NotEqual(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeEpisodeForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Episode = 1 + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + Episode = 26 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Episode, baseReults.Episode); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Episode, updatedResults.Episode); + + // Assert that only the episode has been changed + Assert.NotEqual(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeStatusForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Status = AnimeCompletionStatus.Watching + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + Status = AnimeCompletionStatus.Completed + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Status, baseReults.Status); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Status, updatedResults.Status); + + // Assert that only the status has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.NotEqual(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeScoreForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Score = 3 + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + Score = 8 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Score, baseReults.Score); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Score, updatedResults.Score); + + // Assert that only the score has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.NotEqual(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeStorageTypeForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + StorageType = 3 // VHS + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + StorageType = 5 // VHS + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.StorageType, baseReults.StorageType); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.StorageType, updatedResults.StorageType); + + // Assert that only the storage type has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.NotEqual(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeStorageValueForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + StorageValue = 1 + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + StorageValue = 123 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.StorageValue, baseReults.StorageValue); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.StorageValue, updatedResults.StorageValue); + + // Assert that only the storage value has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.NotEqual(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeTimesRewatchedForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + TimesRewatched = 1 + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + TimesRewatched = 2 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.TimesRewatched, baseReults.TimesRewatched); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.TimesRewatched, updatedResults.TimesRewatched); + + // Assert that only the times rewatched has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.NotEqual(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeRewatchValueForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + RewatchValue = 2 // low + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + RewatchValue = 4 // high + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.RewatchValue, baseReults.RewatchValue); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.RewatchValue, updatedResults.RewatchValue); + + // Assert that only the rewatch value has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.NotEqual(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeDateStartForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + DateStart = new DateTime(2017, 10, 1) + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + DateStart = new DateTime(2017, 12, 10) + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.DateStart, baseReults.DateStart); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.DateStart, updatedResults.DateStart); + + // Assert that only the date start has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.NotEqual(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeDateFinishForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + DateFinish = new DateTime(2017, 10, 5) + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + DateFinish = new DateTime(2017, 12, 15) + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.DateFinish, baseReults.DateFinish); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.DateFinish, updatedResults.DateFinish); + + // Assert that only the date finish has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.NotEqual(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimePriorityForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Priority = 0 // low + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + Priority = 1 // medium + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Priority, baseReults.Priority); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Priority, updatedResults.Priority); + + // Assert that only the priority has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.NotEqual(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeEnableDiscussionForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + EnableDiscussion = 0 + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + EnableDiscussion = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.EnableDiscussion, baseReults.EnableDiscussion); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.EnableDiscussion, updatedResults.EnableDiscussion); + + // Assert that only the enable discussion has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.NotEqual(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeEnableRewatchingForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + EnableRewatching = 0 + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + EnableRewatching = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.EnableRewatching, baseReults.EnableRewatching); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.EnableRewatching, updatedResults.EnableRewatching); + + // Assert that only the enable rewatching has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.NotEqual(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeCommentsForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Comments = "base comment,base comment 2" + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + Comments = "test updated comment, test updated comment2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Comments, baseReults.Comments); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Comments, updatedResults.Comments); + + // Assert that only the comments has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.NotEqual(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateAnimeTagsForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Tags = "test base tag, test base tag 2" + }; + + AnimeUpdate partialUpdateInfo = new AnimeUpdate() + { + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate baseReults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Tags, baseReults.Tags); + + + result = api.UpdateAnimeForUser(animeId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + AnimeUpdate updatedResults = helper.GetUserAnimeDetailsAsync(username, animeId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Tags, updatedResults.Tags); + + // Assert that only the tags has been changed + Assert.Equal(baseReults.Episode, updatedResults.Episode); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.StorageType, updatedResults.StorageType); + Assert.Equal(baseReults.StorageValue, updatedResults.StorageValue); + Assert.Equal(baseReults.TimesRewatched, updatedResults.TimesRewatched); + Assert.Equal(baseReults.RewatchValue, updatedResults.RewatchValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRewatching, updatedResults.EnableRewatching); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.NotEqual(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void GetAnimeListForUserCanceled() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate updateInfo = new AnimeUpdate() + { + Episode = 26, + Status = AnimeCompletionStatus.Completed, + Score = 8, + StorageType = 5, // VHS + StorageValue = 123, + TimesRewatched = 2, + RewatchValue = 4, // high + DateStart = new DateTime(2017, 12, 10), + DateFinish = new DateTime(2017, 12, 15), + Priority = 1, // medium + EnableDiscussion = 1, + EnableRewatching = 1, + Comments = "test updated comment, test updated comment2", + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + CancellationTokenSource tokenSource = new CancellationTokenSource(); + Task userLookupTask = + api.UpdateAnimeForUserAsync(animeId, updateInfo, username, password, tokenSource.Token); + tokenSource.Cancel(); + Assert.Throws(() => userLookupTask.GetAwaiter().GetResult()); + } + } + + [Fact] + public void IncorrectUsernameAnimeEpisodeUpdateTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + username += "test"; + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Cowboy Bebop + int animeId = 1; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Episode = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + Assert.Throws(() => helper.Login(username, password)); + + Assert.Throws(() => api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password)); + } + } + } + + [Fact] + public void WrongAnimeIdUpdateTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + int animeId = 2; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Episode = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + Assert.Throws(() => api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password)); + } + } + } + + [Fact] + public void TestTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + int animeId = 7; + + AnimeUpdate partialBaseInfo = new AnimeUpdate() + { + Episode = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserAnimeDetailsTest helper = new GetUserAnimeDetailsTest()) + { + helper.Login(username, password); + + string test = api.UpdateAnimeForUser(animeId, partialBaseInfo, username, password); + } + } + } + } +} diff --git a/MalApi.IntegrationTests/UpdateUserMangaTest.cs b/MalApi.IntegrationTests/UpdateUserMangaTest.cs new file mode 100644 index 0000000..10dc027 --- /dev/null +++ b/MalApi.IntegrationTests/UpdateUserMangaTest.cs @@ -0,0 +1,977 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Xunit; + +namespace MalApi.IntegrationTests +{ + public class UpdateUserMangaTest + { + [Fact] + public void UpdateMangaForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate baseInfo = new MangaUpdate() + { + Chapter = 1, + Volume = 2, + Status = MangaCompletionStatus.Reading, + Score = 3, + TimesReread = 1, + RereadValue = 2, // low + DateStart = new DateTime(2017, 10, 1), + DateFinish = new DateTime(2017, 10, 5), + Priority = 0, // low + EnableDiscussion = 0, + EnableRereading = 0, + Comments = "base comment,base comment 2", + // ScanGroup = "scan_group", + Tags = "test base tag, test base tag 2" + }; + + MangaUpdate updateInfo = new MangaUpdate() + { + Chapter = 162, + Volume = 18, + Status = MangaCompletionStatus.Completed, + Score = 10, + TimesReread = 2, + RereadValue = 4, // high + DateStart = new DateTime(2017, 12, 10), + DateFinish = new DateTime(2017, 12, 15), + Priority = 1, // medium + EnableDiscussion = 1, + EnableRereading = 1, + Comments = "test updated comment, test updated comment2", + // ScanGroup = "scan_group_updated", + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, baseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(baseInfo.Chapter, baseReults.Chapter); + Assert.Equal(baseInfo.Volume, baseReults.Volume); + Assert.Equal(baseInfo.Status, baseReults.Status); + Assert.Equal(baseInfo.Score, baseReults.Score); + Assert.Equal(baseInfo.TimesReread, baseReults.TimesReread); + Assert.Equal(baseInfo.RereadValue, baseReults.RereadValue); + Assert.Equal(baseInfo.DateStart, baseReults.DateStart); + Assert.Equal(baseInfo.DateFinish, baseReults.DateFinish); + Assert.Equal(baseInfo.Priority, baseReults.Priority); + Assert.Equal(baseInfo.EnableDiscussion, baseReults.EnableDiscussion); + Assert.Equal(baseInfo.EnableRereading, baseReults.EnableRereading); + Assert.Equal(baseInfo.Comments, baseReults.Comments); + Assert.Equal(baseInfo.Tags, baseReults.Tags); + + result = api.UpdateMangaForUser(mangaId, updateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(updateInfo.Chapter, updatedResults.Chapter); + Assert.Equal(updateInfo.Volume, updatedResults.Volume); + Assert.Equal(updateInfo.Status, updatedResults.Status); + Assert.Equal(updateInfo.Score, updatedResults.Score); + Assert.Equal(updateInfo.TimesReread, updatedResults.TimesReread); + Assert.Equal(updateInfo.RereadValue, updatedResults.RereadValue); + Assert.Equal(updateInfo.DateStart, updatedResults.DateStart); + Assert.Equal(updateInfo.DateFinish, updatedResults.DateFinish); + Assert.Equal(updateInfo.Priority, updatedResults.Priority); + Assert.Equal(updateInfo.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(updateInfo.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(updateInfo.Comments, updatedResults.Comments); + Assert.Equal(updateInfo.Tags, updatedResults.Tags); + + // Assert all values have been changed + Assert.NotEqual(baseReults.Chapter, updatedResults.Chapter); + Assert.NotEqual(baseReults.Volume, updatedResults.Volume); + Assert.NotEqual(baseReults.Status, updatedResults.Status); + Assert.NotEqual(baseReults.Score, updatedResults.Score); + Assert.NotEqual(baseReults.TimesReread, updatedResults.TimesReread); + Assert.NotEqual(baseReults.RereadValue, updatedResults.RereadValue); + Assert.NotEqual(baseReults.DateStart, updatedResults.DateStart); + Assert.NotEqual(baseReults.DateFinish, updatedResults.DateFinish); + Assert.NotEqual(baseReults.Priority, updatedResults.Priority); + Assert.NotEqual(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.NotEqual(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.NotEqual(baseReults.Comments, updatedResults.Comments); + Assert.NotEqual(baseReults.Tags, updatedResults.Tags); + + } + } + } + + [Fact] + public void UpdateMangaChapterForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Chapter = 1 + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + Chapter = 162 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Chapter, baseReults.Chapter); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Chapter, updatedResults.Chapter); + + // Assert that only the chapter has been changed + Assert.NotEqual(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaVolumeForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Volume = 2 + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + Volume = 18 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Volume, baseReults.Volume); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Volume, updatedResults.Volume); + + // Assert that only the volume has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.NotEqual(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaStatusForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Status = MangaCompletionStatus.Reading + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + Status = MangaCompletionStatus.Completed + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Status, baseReults.Status); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Status, updatedResults.Status); + + // Assert that only the status has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.NotEqual(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaScoreForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Score = 3 + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + Score = 10 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Score, baseReults.Score); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Score, updatedResults.Score); + + // Assert that only the score has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.NotEqual(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaTimesRereadForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + TimesReread = 1 + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + TimesReread = 2 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.TimesReread, baseReults.TimesReread); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.TimesReread, updatedResults.TimesReread); + + // Assert that only the times reread has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.NotEqual(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaRereadValueForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + RereadValue = 2 // high + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + RereadValue = 4, // high + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.RereadValue, baseReults.RereadValue); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.RereadValue, updatedResults.RereadValue); + + // Assert that only the rearead value has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.NotEqual(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaDateStartForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + DateStart = new DateTime(2017, 10, 1) + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + DateStart = new DateTime(2017, 12, 10) + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.DateStart, baseReults.DateStart); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.DateStart, updatedResults.DateStart); + + // Assert that only the date start has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.NotEqual(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaDateFinishForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + DateFinish = new DateTime(2017, 10, 5) + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + DateFinish = new DateTime(2017, 12, 15) + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.DateFinish, baseReults.DateFinish); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.DateFinish, updatedResults.DateFinish); + + // Assert that only the date finish has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.NotEqual(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaPriorityForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Priority = 0, // low + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + Priority = 1, // medium + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Priority, baseReults.Priority); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Priority, updatedResults.Priority); + + // Assert that only the priority has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.NotEqual(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaEnableDiscussionForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + EnableDiscussion = 0 + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + EnableDiscussion = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.EnableDiscussion, baseReults.EnableDiscussion); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.EnableDiscussion, updatedResults.EnableDiscussion); + + // Assert that only the enable discussion has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.NotEqual(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaEnableRereadingForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + EnableRereading = 0 + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + EnableRereading = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.EnableRereading, baseReults.EnableRereading); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.EnableRereading, updatedResults.EnableRereading); + + // Assert that only the enable rereading has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.NotEqual(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaCommentsorUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Comments = "base comment,base comment 2" + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + Comments = "test updated comment, test updated comment2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Comments, baseReults.Comments); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Comments, updatedResults.Comments); + + // Assert that only the comments has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.NotEqual(baseReults.Comments, updatedResults.Comments); + Assert.Equal(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaTagsForUserTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Tags = "test base tag, test base tag 2" + }; + + MangaUpdate partialUpdateInfo = new MangaUpdate() + { + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + string result = api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate baseReults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert first update against base info + Assert.Equal(partialBaseInfo.Tags, baseReults.Tags); + + result = api.UpdateMangaForUser(mangaId, partialUpdateInfo, username, password); + Assert.Equal("Updated", result); + + MangaUpdate updatedResults = helper.GetUserMangaDetailsAsync(username, mangaId); + + // Assert second update with update info + Assert.Equal(partialUpdateInfo.Tags, updatedResults.Tags); + + // Assert that only the tags has been changed + Assert.Equal(baseReults.Chapter, updatedResults.Chapter); + Assert.Equal(baseReults.Volume, updatedResults.Volume); + Assert.Equal(baseReults.Status, updatedResults.Status); + Assert.Equal(baseReults.Score, updatedResults.Score); + Assert.Equal(baseReults.TimesReread, updatedResults.TimesReread); + Assert.Equal(baseReults.RereadValue, updatedResults.RereadValue); + Assert.Equal(baseReults.DateStart, updatedResults.DateStart); + Assert.Equal(baseReults.DateFinish, updatedResults.DateFinish); + Assert.Equal(baseReults.Priority, updatedResults.Priority); + Assert.Equal(baseReults.EnableDiscussion, updatedResults.EnableDiscussion); + Assert.Equal(baseReults.EnableRereading, updatedResults.EnableRereading); + Assert.Equal(baseReults.Comments, updatedResults.Comments); + Assert.NotEqual(baseReults.Tags, updatedResults.Tags); + } + } + } + + [Fact] + public void UpdateMangaForUserCanceled() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate updateInfo = new MangaUpdate() + { + Chapter = 162, + Volume = 18, + Status = MangaCompletionStatus.Completed, + Score = 10, + TimesReread = 2, + RereadValue = 4, // high + DateStart = new DateTime(2017, 12, 10), + DateFinish = new DateTime(2017, 12, 15), + Priority = 1, // medium + EnableDiscussion = 1, + EnableRereading = 1, + Comments = "test updated comment, test updated comment2", + // ScanGroup = "scan_group_updated", + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + CancellationTokenSource tokenSource = new CancellationTokenSource(); + Task userLookupTask = + api.UpdateMangaForUserAsync(mangaId, updateInfo, username, password, tokenSource.Token); + tokenSource.Cancel(); + Assert.Throws(() => userLookupTask.GetAwaiter().GetResult()); + } + } + + [Fact] + public void IncorrectUsernameMangaChapterUpdateTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + username += "test"; + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + // Monster + int mangaId = 1; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Chapter = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + Assert.Throws(() => helper.Login(username, password)); + + Assert.Throws(() => api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password)); + } + } + } + + [Fact] + public void WrongMangaIdUpdateTest() + { + string username = System.Environment.GetEnvironmentVariable("MAL_USERNAME"); + string password = System.Environment.GetEnvironmentVariable("MAL_PASSWORD"); + + int mangaId = 5; + + MangaUpdate partialBaseInfo = new MangaUpdate() + { + Chapter = 1 + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + using (GetUserMangaDetailsTest helper = new GetUserMangaDetailsTest()) + { + helper.Login(username, password); + + Assert.Throws(() => api.UpdateMangaForUser(mangaId, partialBaseInfo, username, password)); + } + } + } + } +} diff --git a/MalApi.IntegrationTests/readme.txt b/MalApi.IntegrationTests/readme.txt index ea33859..d195066 100644 --- a/MalApi.IntegrationTests/readme.txt +++ b/MalApi.IntegrationTests/readme.txt @@ -1 +1,14 @@ -No special setup is needed in order to run these tests. MAL seems to no longer require an API key. \ No newline at end of file +MAL seems to no longer require an API key. + +In order for some tests to pass environmental variables need to be set. +These variable are the username and passowrd of a MAL account. +The requirements towards the account is that it has the 'Cowboy Bebop' anime and the 'Monster' manga added to it. + +Steps to set up environmental variables: +1) Follow these instructions to get to the needed window: https://serverfault.com/q/351129 or try searching for 'environment' in the start menu, if you'd like to save those precious seconds. + +2) Add two new user variables called 'MAL_USERNAME' and 'MAL_PASSWORD' with the appropriate values for the account you wish to be used with these tests. + +3) Done! Go run the tests and see if everything's OK. + +If the scraping methods in the integration test project are changed (ScrapeUserAnimeDetailsFromHtml and ScrapeUserMangaDetailsFromHtml) be sure to also update them in the unit test project (MalDetailScrapingUtils.cs). diff --git a/MalApi.NetCoreExample/Program.cs b/MalApi.NetCoreExample/Program.cs index b48ef4e..a1691e6 100644 --- a/MalApi.NetCoreExample/Program.cs +++ b/MalApi.NetCoreExample/Program.cs @@ -1,5 +1,4 @@ -using MalApi; -using System; +using System; namespace MalApi.NetCoreExample { diff --git a/MalApi.UnitTests/AnimeListCacheTests.cs b/MalApi.UnitTests/AnimeListCacheTests.cs index bb8c629..7232d6d 100644 --- a/MalApi.UnitTests/AnimeListCacheTests.cs +++ b/MalApi.UnitTests/AnimeListCacheTests.cs @@ -15,7 +15,7 @@ public void TestCacheCaseInsensitivity() { using (AnimeListCache cache = new AnimeListCache(expiration: TimeSpan.FromHours(5))) { - cache.PutListForUser("a", new MalUserLookupResults(userId: 5, canonicalUserName: "A", animeList: new List())); + cache.PutListForUser("a", new MalUserLookupResults(userId: 5, canonicalUserName: "A", animeList: new List(), mangaList: new List())); cache.GetListForUser("A", out MalUserLookupResults lookup); Assert.Equal(5, lookup.UserId); } diff --git a/MalApi.UnitTests/Cowboy_Bebop.htm b/MalApi.UnitTests/Cowboy_Bebop.htm new file mode 100644 index 0000000..27ded71 --- /dev/null +++ b/MalApi.UnitTests/Cowboy_Bebop.htm @@ -0,0 +1 @@ + MyAnimeList.net

Edit Anime

Edit Anime
Anime Title Cowboy Bebop
Status
Episodes Watched + / 26 History
Your Score
Start Date Month: Day: Year: Insert Today
Finish Date Month: Day: Year: Insert Today
Hide Advanced
Show Advanced
Tags
Priority
Storage
Total Times
Re-watched Series
Rewatch Value
Comments
Ask to Discuss?
Post to SNS
\ No newline at end of file diff --git a/MalApi.UnitTests/MalApi.UnitTests.csproj b/MalApi.UnitTests/MalApi.UnitTests.csproj index 7c83170..62fc469 100644 --- a/MalApi.UnitTests/MalApi.UnitTests.csproj +++ b/MalApi.UnitTests/MalApi.UnitTests.csproj @@ -10,16 +10,21 @@ true - - + + + + + + + - - + + @@ -31,10 +36,16 @@
+ + Never + - - + + + + + diff --git a/MalApi.UnitTests/MalAppInfoXmlTests.cs b/MalApi.UnitTests/MalAppInfoXmlTests.cs index bad5cd3..bd7da81 100644 --- a/MalApi.UnitTests/MalAppInfoXmlTests.cs +++ b/MalApi.UnitTests/MalAppInfoXmlTests.cs @@ -14,19 +14,37 @@ public partial class MalAppInfoXmlTests [Fact] public void ParseWithTextReaderTest() { - using (TextReader reader = Helpers.GetResourceStream("test.xml")) + using (TextReader reader = Helpers.GetResourceStream("test_anime.xml")) { MalUserLookupResults results = MalAppInfoXml.Parse(reader); - DoAsserts(results); + DoAnimeAsserts(results); } } [Fact] - public void ParseWithXElementTest() + public void ParseWithTextReaderMangaTest() { - XDocument doc = XDocument.Parse(Helpers.GetResourceText("test_clean.xml")); - MalUserLookupResults results = MalAppInfoXml.Parse(doc); - DoAsserts(results); + using (TextReader reader = Helpers.GetResourceStream("test_manga.xml")) + { + MalUserLookupResults results = MalAppInfoXml.Parse(reader); + DoMangaAsserts(results); + } + } + + [Fact] + public void ParseWithXElementAnimeTest() + { + XDocument doc = XDocument.Parse(Helpers.GetResourceText("test_anime_clean.xml")); + MalUserLookupResults results = MalAppInfoXml.ParseResults(doc); + DoAnimeAsserts(results); + } + + [Fact] + public void ParseWithXElementMangaTest() + { + XDocument doc = XDocument.Parse(Helpers.GetResourceText("test_manga_clean.xml")); + MalUserLookupResults results = MalAppInfoXml.ParseResults(doc); + DoMangaAsserts(results); } [Fact] @@ -42,7 +60,7 @@ public void ParseInvalidUserWithTextReaderTest() public void ParseInvalidUserWithXElementTest() { XDocument doc = XDocument.Parse(Helpers.GetResourceText("test_no_such_user.xml")); - Assert.Throws(() => MalAppInfoXml.Parse(doc)); + Assert.Throws(() => MalAppInfoXml.ParseResults(doc)); } [Fact] @@ -58,10 +76,10 @@ public void ParseOldInvalidUserWithTextReaderTest() public void ParseOldInvalidUserWithXElementTest() { XDocument doc = XDocument.Parse(Helpers.GetResourceText("test_no_such_user_old.xml")); - Assert.Throws(() => MalAppInfoXml.Parse(doc)); + Assert.Throws(() => MalAppInfoXml.ParseResults(doc)); } - private void DoAsserts(MalUserLookupResults results) + private void DoAnimeAsserts(MalUserLookupResults results) { Assert.Equal("LordHighCaptain", results.CanonicalUserName); Assert.Equal(158667, results.UserId); @@ -74,7 +92,7 @@ private void DoAsserts(MalUserLookupResults results) Assert.Equal(7, entry.NumEpisodesWatched); Assert.Equal(7, entry.Score); - Assert.Equal(CompletionStatus.Watching, entry.Status); + Assert.Equal(AnimeCompletionStatus.Watching, entry.Status); // Test tags with Equal, not equivalent, because order in tags matters Assert.Equal(new List() { "duck", "goose" }, entry.Tags); @@ -85,7 +103,7 @@ private void DoAsserts(MalUserLookupResults results) entry.AnimeInfo.Synonyms.Should().BeEquivalentTo(new List() { "The Vanishment of Haruhi Suzumiya", "Suzumiya Haruhi no Syoshitsu", "Haruhi movie", "The Disappearance of Haruhi Suzumiya" }); Assert.Equal((decimal?)null, entry.Score); Assert.Equal(0, entry.NumEpisodesWatched); - Assert.Equal(CompletionStatus.PlanToWatch, entry.Status); + Assert.Equal(AnimeCompletionStatus.PlanToWatch, entry.Status); Assert.Equal(new List(), entry.Tags); entry = results.AnimeList.Where(anime => anime.AnimeInfo.AnimeId == 889).First(); @@ -106,6 +124,51 @@ private void DoAsserts(MalUserLookupResults results) Assert.Equal(new List() { "test&test", "< less than", "> greater than", "apos '", "quote \"", "hex ö", "dec !", "control character" }, entry.Tags); } + + private void DoMangaAsserts(MalUserLookupResults results) + { + Assert.Equal("naps250", results.CanonicalUserName); + Assert.Equal(5544903, results.UserId); + Assert.Equal(6, results.MangaList.Count); + + MyMangaListEntry entry = results.MangaList.Where(manga => manga.MangaInfo.MangaId == 2).First(); + Assert.Equal("Berserk", entry.MangaInfo.Title); + Assert.Equal(MalMangaType.Manga, entry.MangaInfo.Type); + entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List() { "Berserk: The Prototype" }); + + Assert.Equal(352, entry.NumChaptersRead); + Assert.Equal(10, entry.Score); + Assert.Equal(MangaCompletionStatus.Reading, entry.Status); + + // Test tags with Equal, not equivalent, because order in tags matters + Assert.Equal(new List() { "CLANG", "Miura pls" }, entry.Tags); + + entry = results.MangaList.Where(manga => manga.MangaInfo.MangaId == 9115).First(); + Assert.Equal("Ookami to Koushinryou", entry.MangaInfo.Title); + Assert.Equal(MalMangaType.Novel, entry.MangaInfo.Type); + entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List() { "Okami to Koshinryo", "Spice and Wolf", "Spice & Wolf" }); + Assert.Equal((decimal?)null, entry.Score); + Assert.Equal(0, entry.NumChaptersRead); + Assert.Equal(MangaCompletionStatus.Completed, entry.Status); + Assert.Equal(new List(), entry.Tags); + + entry = results.MangaList.Where(manga => manga.MangaInfo.MangaId == 1).First(); + Assert.Equal("Monster", entry.MangaInfo.Title); + + // Make sure synonyms that are the same as the real name get filtered out + entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List()); + + entry = results.MangaList.Where(manga => manga.MangaInfo.Title == "Test").First(); + // Make sure that is the same as + entry.MangaInfo.Synonyms.Should().BeEquivalentTo(new List()); + Assert.Equal(new UncertainDate(2010, 2, 6), entry.MangaInfo.StartDate); + Assert.Equal(UncertainDate.Unknown, entry.MangaInfo.EndDate); + Assert.Equal("https://myanimelist.cdn-dena.com/images/manga/2/159423.jpg", entry.MangaInfo.ImageUrl); + Assert.Equal(new UncertainDate(year: null, month: 2, day: null), entry.MyStartDate); + Assert.Equal(UncertainDate.Unknown, entry.MyFinishDate); + Assert.Equal(new DateTime(year: 2011, month: 4, day: 2, hour: 22, minute: 50, second: 58, kind: DateTimeKind.Utc), entry.MyLastUpdate); + Assert.Equal(new List() { "test&test", "< less than", "> greater than", "apos '", "quote \"", "hex ö", "dec !", "control character" }, entry.Tags); + } } } diff --git a/MalApi.UnitTests/MalDetailScrapingUtils.cs b/MalApi.UnitTests/MalDetailScrapingUtils.cs new file mode 100644 index 0000000..8964797 --- /dev/null +++ b/MalApi.UnitTests/MalDetailScrapingUtils.cs @@ -0,0 +1,349 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using HtmlAgilityPack; + +namespace MalApi.UnitTests +{ + class MalDetailScrapingUtils + { + public static AnimeUpdate ScrapeUserAnimeDetailsFromHtml(string userAnimeDetailsHtml) + { + AnimeUpdate results = new AnimeUpdate(); + + var doc = new HtmlDocument(); + doc.LoadHtml(userAnimeDetailsHtml); + + // Episode + results.Episode = doc.GetElementbyId("add_anime_num_watched_episodes").GetAttributeValue("value", -1); + + // Status + var parentNode = doc.GetElementbyId("add_anime_status"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Status = (AnimeCompletionStatus)childNode.GetAttributeValue("value", -1); + break; + } + } + + // Enable rewatching + results.EnableRewatching = doc.GetElementbyId("add_anime_is_rewatching").Attributes["checked"] == null ? 0 : 1; + + // Score + parentNode = doc.GetElementbyId("add_anime_score"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Score = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Start date + int day = -1; + int month = -1; + int year = -1; + parentNode = doc.GetElementbyId("add_anime_start_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_start_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_start_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateStart = null; + } + else + { + results.DateStart = new DateTime(year, month, day); + } + + // Date finish + parentNode = doc.GetElementbyId("add_anime_finish_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_finish_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_anime_finish_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateFinish = null; + } + else + { + results.DateFinish = new DateTime(year, month, day); + } + + // Tags + results.Tags = doc.GetElementbyId("add_anime_tags").InnerText; + + // Priority + parentNode = doc.GetElementbyId("add_anime_priority"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Priority = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Storage type + parentNode = doc.GetElementbyId("add_anime_storage_type"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.StorageType = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Storage value + results.StorageValue = doc.GetElementbyId("add_anime_storage_value").GetAttributeValue("value", -1); + + // Times rewatched + results.TimesRewatched = doc.GetElementbyId("add_anime_num_watched_times").GetAttributeValue("value", -1); + + // Rewatch value + parentNode = doc.GetElementbyId("add_anime_rewatch_value"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.RewatchValue = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Comments + results.Comments = doc.GetElementbyId("add_anime_comments").InnerText; + + // Enable discussion + parentNode = doc.GetElementbyId("add_anime_is_asked_to_discuss"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + // Because 'Enable discussion = 1' sent for update sets the first options of the dropdown, which corresponds to 0 and vice versa... + int temp = childNode.GetAttributeValue("value", -1); + temp = temp == 1 ? 0 : 1; + results.EnableDiscussion = temp; + break; + } + } + + return results; + } + + public static MangaUpdate ScrapeUserMangaDetailsFromHtml(string userMangaDetailsHtml) + { + MangaUpdate results = new MangaUpdate(); + + var doc = new HtmlDocument(); + doc.LoadHtml(userMangaDetailsHtml); + + // Chapter + results.Chapter = doc.GetElementbyId("add_manga_num_read_chapters").GetAttributeValue("value", -1); + + // Volume + results.Volume = doc.GetElementbyId("add_manga_num_read_volumes").GetAttributeValue("value", -1); + + // Status + var parentNode = doc.GetElementbyId("add_manga_status"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Status = (MangaCompletionStatus)childNode.GetAttributeValue("value", -1); + break; + } + } + + // Enable rereading + results.EnableRereading = doc.GetElementbyId("add_manga_is_rereading").Attributes["checked"] == null ? 0 : 1; + + // Score + parentNode = doc.GetElementbyId("add_manga_score"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Score = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Start date + int day = -1; + int month = -1; + int year = -1; + parentNode = doc.GetElementbyId("add_manga_start_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_start_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_start_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateStart = null; + } + else + { + results.DateStart = new DateTime(year, month, day); + } + + // Date finish + parentNode = doc.GetElementbyId("add_manga_finish_date_month"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + month = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_finish_date_day"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + day = childNode.GetAttributeValue("value", -1); + break; + } + } + parentNode = doc.GetElementbyId("add_manga_finish_date_year"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + year = childNode.GetAttributeValue("value", -1); + break; + } + } + if (month == -1 || day == -1 || year == -1) + { + results.DateFinish = null; + } + else + { + results.DateFinish = new DateTime(year, month, day); + } + + // Tags + results.Tags = doc.GetElementbyId("add_manga_tags").InnerText; + + // Priority + parentNode = doc.GetElementbyId("add_manga_priority"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.Priority = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Times reread + results.TimesReread = doc.GetElementbyId("add_manga_num_read_times").GetAttributeValue("value", -1); + + // Reread value + parentNode = doc.GetElementbyId("add_manga_reread_value"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + results.RereadValue = childNode.GetAttributeValue("value", -1); + break; + } + } + + // Comments + results.Comments = doc.GetElementbyId("add_manga_comments").InnerText; + + // Enable discussion + parentNode = doc.GetElementbyId("add_manga_is_asked_to_discuss"); + foreach (var childNode in parentNode.ChildNodes) + { + if (childNode.Attributes["selected"] != null) + { + // Because 'Enable discussion = 1' sent for update sets the first options of the dropdown, which corresponds to 0 and vice versa... + int temp = childNode.GetAttributeValue("value", -1); + temp = temp == 1 ? 0 : 1; + results.EnableDiscussion = temp; + break; + } + } + + return results; + } + } +} diff --git a/MalApi.UnitTests/Monster.htm b/MalApi.UnitTests/Monster.htm new file mode 100644 index 0000000..380e5d4 --- /dev/null +++ b/MalApi.UnitTests/Monster.htm @@ -0,0 +1 @@ + MyAnimeList.net

Edit Manga

Edit Manga
Manga Title Monster
Status
Volumes Read + / 18
Chapters Read + / 162 History
Your Score
Start Date Month: Day: Year: Insert Today
Finish Date Month: Day: Year: Insert Today
Hide Advanced
Show Advanced
Tags
Priority
Storage
Total Times
Re-read
Re-read Value
Comments
Ask to Discuss?
Post to SNS
\ No newline at end of file diff --git a/MalApi.UnitTests/MyAnimeListApiTests.cs b/MalApi.UnitTests/MyAnimeListApiTests.cs index efb50ba..768e6b6 100644 --- a/MalApi.UnitTests/MyAnimeListApiTests.cs +++ b/MalApi.UnitTests/MyAnimeListApiTests.cs @@ -34,6 +34,101 @@ public void TestScrapeAnimeDetailsFromHtml() results.Genres.Should().BeEquivalentTo(expectedGenres); } } + + [Fact] + public void TestScrapeUserAnimeDetailsFromHtml() + { + string html; + using (StreamReader reader = Helpers.GetResourceStream("Cowboy_Bebop.htm")) + { + html = reader.ReadToEnd(); + } + + AnimeUpdate info = new AnimeUpdate() + { + Episode = 26, + Status = AnimeCompletionStatus.Completed, + Score = 8, + StorageType = 5, // VHS + StorageValue = 123, + TimesRewatched = 2, + RewatchValue = 4, // high + DateStart = new DateTime(2017, 12, 10), + DateFinish = new DateTime(2017, 12, 15), + Priority = 1, // medium + EnableDiscussion = 1, + EnableRewatching = 1, + Comments = "test updated comment, test updated comment2", + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + AnimeUpdate results = MalDetailScrapingUtils.ScrapeUserAnimeDetailsFromHtml(html); + + Assert.Equal(info.Episode, results.Episode); + Assert.Equal(info.Status, results.Status); + Assert.Equal(info.Score, results.Score); + Assert.Equal(info.StorageType, results.StorageType); + Assert.Equal(info.StorageValue, results.StorageValue); + Assert.Equal(info.TimesRewatched, results.TimesRewatched); + Assert.Equal(info.RewatchValue, results.RewatchValue); + Assert.Equal(info.DateStart, results.DateStart); + Assert.Equal(info.DateFinish, results.DateFinish); + Assert.Equal(info.Priority, results.Priority); + Assert.Equal(info.EnableDiscussion, results.EnableDiscussion); + Assert.Equal(info.EnableRewatching, results.EnableRewatching); + Assert.Equal(info.Comments, results.Comments); + Assert.Equal(info.Tags, results.Tags); + } + } + + [Fact] + public void TestScrapeUserMangaDetailsFromHtml() + { + string html; + using (StreamReader reader = Helpers.GetResourceStream("Monster.htm")) + { + html = reader.ReadToEnd(); + } + + MangaUpdate info = new MangaUpdate() + { + Chapter = 162, + Volume = 18, + Status = MangaCompletionStatus.Completed, + Score = 10, + TimesReread = 2, + RereadValue = 4, // high + DateStart = new DateTime(2017, 12, 10), + DateFinish = new DateTime(2017, 12, 15), + Priority = 1, // medium + EnableDiscussion = 1, + EnableRereading = 1, + Comments = "test updated comment, test updated comment2", + // ScanGroup = "scan_group_updated", + Tags = "test updated tag, test updated tag 2" + }; + + using (MyAnimeListApi api = new MyAnimeListApi()) + { + MangaUpdate results = MalDetailScrapingUtils.ScrapeUserMangaDetailsFromHtml(html); + + Assert.Equal(info.Chapter, results.Chapter); + Assert.Equal(info.Volume, results.Volume); + Assert.Equal(info.Status, results.Status); + Assert.Equal(info.Score, results.Score); + Assert.Equal(info.TimesReread, results.TimesReread); + Assert.Equal(info.RereadValue, results.RereadValue); + Assert.Equal(info.DateStart, results.DateStart); + Assert.Equal(info.DateFinish, results.DateFinish); + Assert.Equal(info.Priority, results.Priority); + Assert.Equal(info.EnableDiscussion, results.EnableDiscussion); + Assert.Equal(info.EnableRereading, results.EnableRereading); + Assert.Equal(info.Comments, results.Comments); + Assert.Equal(info.Tags, results.Tags); + } + } } } diff --git a/MalApi.UnitTests/test.xml b/MalApi.UnitTests/test_anime.xml similarity index 100% rename from MalApi.UnitTests/test.xml rename to MalApi.UnitTests/test_anime.xml diff --git a/MalApi.UnitTests/test_clean.xml b/MalApi.UnitTests/test_anime_clean.xml similarity index 100% rename from MalApi.UnitTests/test_clean.xml rename to MalApi.UnitTests/test_anime_clean.xml diff --git a/MalApi.UnitTests/test_manga.xml b/MalApi.UnitTests/test_manga.xml new file mode 100644 index 0000000..ed0e905 --- /dev/null +++ b/MalApi.UnitTests/test_manga.xml @@ -0,0 +1,151 @@ + + + + 5544903 + naps250 + 22 + 48 + 2 + 3 + 62 + 38.62 + + + 2 + Berserk + Berserk: The Prototype; Berserk + 1 + 0 + 0 + 1 + 1989-08-25 + 0000-00-00 + https://myanimelist.cdn-dena.com/images/manga/1/157931.jpg + 51710571 + 352 + 38 + 0000-00-00 + 0000-00-00 + 10 + 1 + 0 + 0 + 1515800570 + CLANG,, Miura pls + + + 9115 + Ookami to Koushinryou + Okami to Koshinryo; Spice and Wolf; Spice & Wolf + 2 + 0 + 0 + 1 + 2006-02-10 + 0000-00-00 + https://myanimelist.cdn-dena.com/images/manga/2/153860.jpg + 58175226 + 0 + 17 + 0000-00-00 + 0000-00-00 + 0 + 2 + 0 + 0 + 1492361419 + + + + 1 + Monster + ; Monster + 1 + 162 + 18 + 2 + 1994-12-05 + 2001-12-20 + https://myanimelist.cdn-dena.com/images/manga/3/54525.jpg + 51710604 + 1 + 18 + 2017-12-10 + 2017-12-15 + 10 + 2 + 1 + 0 + 1516443916 + test&test, < less than, > greater than, apos ', quote ", hex ö, dec !, control character + + + 988 + Shijou Saikyou no Deshi Kenichi + History's Strongest Disciple Kenichi; Kenichi: The Mightiest Disciple + 1 + 584 + 61 + 2 + 2002-08-09 + 2014-09-17 + https://myanimelist.cdn-dena.com/images/manga/2/169883.jpg + 51710624 + 584 + 61 + 0000-00-00 + 2017-01-28 + 8 + 2 + 0 + 0 + 1485582298 + + + + 12 + Bleach + ; Bleach + 1 + 705 + 74 + 2 + 2001-08-07 + 2016-08-22 + https://myanimelist.cdn-dena.com/images/manga/2/180089.jpg + 51710858 + 705 + 74 + 0000-00-00 + 2017-01-28 + 7 + 2 + 0 + 0 + 1485582249 + + + + 999999 + Test + + 1 + 36 + 3 + 2 + 2010-02-06 + 0000-00-00 + https://myanimelist.cdn-dena.com/images/manga/2/159423.jpg + 58002197 + 36 + 3 + 0000-02-00 + 0000-00-00 + 5 + 2 + 0 + 0 + 1301784658 + test&test, < less than, > greater than, apos ', quote ", hex ö, dec !, control character + + \ No newline at end of file diff --git a/MalApi.UnitTests/test_manga_clean.xml b/MalApi.UnitTests/test_manga_clean.xml new file mode 100644 index 0000000..4d725d0 --- /dev/null +++ b/MalApi.UnitTests/test_manga_clean.xml @@ -0,0 +1,151 @@ + + + + 5544903 + naps250 + 22 + 48 + 2 + 3 + 62 + 38.62 + + + 2 + Berserk + Berserk: The Prototype; Berserk + 1 + 0 + 0 + 1 + 1989-08-25 + 0000-00-00 + https://myanimelist.cdn-dena.com/images/manga/1/157931.jpg + 51710571 + 352 + 38 + 0000-00-00 + 0000-00-00 + 10 + 1 + 0 + 0 + 1515800570 + CLANG, Miura pls + + + 9115 + Ookami to Koushinryou + Okami to Koshinryo; Spice and Wolf; Spice & Wolf + 2 + 0 + 0 + 1 + 2006-02-10 + 0000-00-00 + https://myanimelist.cdn-dena.com/images/manga/2/153860.jpg + 58175226 + 0 + 17 + 0000-00-00 + 0000-00-00 + 0 + 2 + 0 + 0 + 1492361419 + + + + 1 + Monster + ; Monster + 1 + 162 + 18 + 2 + 1994-12-05 + 2001-12-20 + https://myanimelist.cdn-dena.com/images/manga/3/54525.jpg + 51710604 + 1 + 18 + 2017-12-10 + 2017-12-15 + 10 + 2 + 1 + 0 + 1516443916 + test updated tag, test updated tag 2 + + + 988 + Shijou Saikyou no Deshi Kenichi + History's Strongest Disciple Kenichi; Kenichi: The Mightiest Disciple + 1 + 584 + 61 + 2 + 2002-08-09 + 2014-09-17 + https://myanimelist.cdn-dena.com/images/manga/2/169883.jpg + 51710624 + 584 + 61 + 0000-00-00 + 2017-01-28 + 8 + 2 + 0 + 0 + 1485582298 + + + + 12 + Bleach + ; Bleach + 1 + 705 + 74 + 2 + 2001-08-07 + 2016-08-22 + https://myanimelist.cdn-dena.com/images/manga/2/180089.jpg + 51710858 + 705 + 74 + 0000-00-00 + 2017-01-28 + 7 + 2 + 0 + 0 + 1485582249 + + + + 999999 + Test + + 1 + 36 + 3 + 2 + 2010-02-06 + 0000-00-00 + https://myanimelist.cdn-dena.com/images/manga/2/159423.jpg + 58002197 + 36 + 3 + 0000-02-00 + 0000-00-00 + 5 + 2 + 0 + 0 + 1301784658 + test&test, < less than, > greater than, apos ', quote ", hex ö, dec !, control character + + \ No newline at end of file diff --git a/MalApi.sln b/MalApi.sln index 080f0ef..88c85b2 100644 --- a/MalApi.sln +++ b/MalApi.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 15 -VisualStudioVersion = 15.0.26430.14 +VisualStudioVersion = 15.0.26430.15 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MalApi", "MalApi\MalApi.csproj", "{85BC477F-4798-4006-A342-715E7565B3BB}" EndProject @@ -18,7 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{00247D .nuget\NuGet.targets = .nuget\NuGet.targets EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MalApi.NetCoreExample", "MalApi.NetCoreExample\MalApi.NetCoreExample.csproj", "{EF72FAC1-AE34-4D7B-9839-4ED50288CFB4}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MalApi.NetCoreExample", "MalApi.NetCoreExample\MalApi.NetCoreExample.csproj", "{EF72FAC1-AE34-4D7B-9839-4ED50288CFB4}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/MalApi/CompletionStatus.cs b/MalApi/Anime/AnimeCompletionStatus.cs similarity index 70% rename from MalApi/CompletionStatus.cs rename to MalApi/Anime/AnimeCompletionStatus.cs index e5754c3..9a9c0b9 100644 --- a/MalApi/CompletionStatus.cs +++ b/MalApi/Anime/AnimeCompletionStatus.cs @@ -2,15 +2,22 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Xml.Serialization; namespace MalApi { - public enum CompletionStatus + // XML enum attributes are needed for proper serialization upon sending an update request + public enum AnimeCompletionStatus { + [XmlEnum(Name = "watching")] Watching = 1, + [XmlEnum(Name = "completed")] Completed = 2, + [XmlEnum(Name = "onhold")] OnHold = 3, + [XmlEnum(Name = "dropped")] Dropped = 4, + [XmlEnum(Name = "plantowatch")] PlanToWatch = 6, } } diff --git a/MalApi/Anime/AnimeUpdate.cs b/MalApi/Anime/AnimeUpdate.cs new file mode 100644 index 0000000..121b22d --- /dev/null +++ b/MalApi/Anime/AnimeUpdate.cs @@ -0,0 +1,117 @@ +using System; +using System.Xml; +using System.Xml.Linq; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace MalApi +{ + /// + /// The update object sent to MAL when updating an anime entry. + /// Only specified values will be changed. The rest will remain unchanged. + /// More details: https://myanimelist.net/modules.php?go=api#animevalues + /// + [XmlRoot("entry")] + public class AnimeUpdate + { + [XmlElement("episode")] + public int? Episode { get; set; } = null; + + [XmlElement("status")] + public AnimeCompletionStatus? Status { get; set; } = null; + + [XmlElement("score")] + public int? Score { get; set; } = null; + + [XmlElement("storage_type")] + public int? StorageType { get; set; } = null; + + [XmlElement("storage_value")] + public float? StorageValue { get; set; } = null; + + [XmlElement("times_rewatched")] + public int? TimesRewatched { get; set; } = null; + + [XmlElement("rewatch_value")] + public int? RewatchValue { get; set; } = null; + + [XmlIgnore] + public DateTime? DateStart { get; set; } = null; + + [XmlElement("date_start")] + private string FormattedDateStart + { + get + { + return DateStart?.ToString("MMddyyyy"); + } + set + { + DateStart = DateTime.Parse(value); + } + } + + public DateTime? DateFinish { get; set; } = null; + + [XmlElement("date_finish")] + private string FormattedDateFinish + { + get + { + return DateFinish?.ToString("MMddyyyy"); + } + set + { + DateFinish = DateTime.Parse(value); + } + } + + [XmlElement("priority")] + public int? Priority { get; set; } = null; + + [XmlElement("enable_discussion")] + public int? EnableDiscussion { get; set; } = null; + + [XmlElement("enable_rewatching")] + public int? EnableRewatching { get; set; } = null; + + [XmlElement("comments")] + public string Comments { get; set; } = null; + + [XmlElement("tags")] + public string Tags { get; set; } = null; + + /// + /// Generates an XML with the current information stored in the object. XML can later be used to update records on MAL. + /// + /// String representation of object-generated XML. + public string GenerateXml() + { + XDocument document = new XDocument( + new XDeclaration("1.0", "UTF-8", null), + new XElement("entry", + Episode != null ? new XElement("episode", Episode) : null, + Status != null ? new XElement("status", (int?)Status) : null, + Score != null ? new XElement("score", Score) : null, + StorageType != null ? new XElement("storage_type", StorageType) : null, + StorageValue != null ? new XElement("storage_value", StorageValue) : null, + TimesRewatched != null ? new XElement("times_rewatched", TimesRewatched) : null, + RewatchValue != null ? new XElement("rewatch_value", RewatchValue) : null, + FormattedDateStart != null ? new XElement("date_start", FormattedDateStart) : null, + FormattedDateFinish != null ? new XElement("date_finish", FormattedDateFinish) : null, + Priority != null ? new XElement("priority", Priority) : null, + EnableDiscussion != null ? new XElement("enable_discussion", EnableDiscussion) : null, + EnableRewatching != null ? new XElement("enable_rewatching", EnableRewatching) : null, + Comments != null ? new XElement("comments", Comments) : null, + Tags != null ? new XElement("tags", Tags) : null + ) + ); + + using (Utf8StringWriter writer = new Utf8StringWriter()) + { + document.Save(writer); + return writer.ToString(); + } + } + } +} diff --git a/MalApi/MalAnimeInfoFromUserLookup.cs b/MalApi/Anime/MalAnimeInfoFromUserLookup.cs similarity index 94% rename from MalApi/MalAnimeInfoFromUserLookup.cs rename to MalApi/Anime/MalAnimeInfoFromUserLookup.cs index eb56bad..a225801 100644 --- a/MalApi/MalAnimeInfoFromUserLookup.cs +++ b/MalApi/Anime/MalAnimeInfoFromUserLookup.cs @@ -16,7 +16,7 @@ public class MalAnimeInfoFromUserLookup : IEquatable public MalAnimeType Type { get; private set; } public ICollection Synonyms { get; private set; } - public MalSeriesStatus Status { get; private set; } + public MalAnimeSeriesStatus Status { get; private set; } /// /// Could be 0 for anime that hasn't aired yet or less than the planned number of episodes for a series currently airing. @@ -27,7 +27,7 @@ public class MalAnimeInfoFromUserLookup : IEquatable public UncertainDate EndDate { get; private set; } public string ImageUrl { get; private set; } - public MalAnimeInfoFromUserLookup(int animeId, string title, MalAnimeType type, ICollection synonyms, MalSeriesStatus status, + public MalAnimeInfoFromUserLookup(int animeId, string title, MalAnimeType type, ICollection synonyms, MalAnimeSeriesStatus status, int numEpisodes, UncertainDate startDate, UncertainDate endDate, string imageUrl) { AnimeId = animeId; diff --git a/MalApi/MalAnimeNotFoundException.cs b/MalApi/Anime/MalAnimeNotFoundException.cs similarity index 100% rename from MalApi/MalAnimeNotFoundException.cs rename to MalApi/Anime/MalAnimeNotFoundException.cs diff --git a/MalApi/MalSeriesStatus.cs b/MalApi/Anime/MalAnimeSeriesStatus.cs similarity index 95% rename from MalApi/MalSeriesStatus.cs rename to MalApi/Anime/MalAnimeSeriesStatus.cs index 8002359..144da1d 100644 --- a/MalApi/MalSeriesStatus.cs +++ b/MalApi/Anime/MalAnimeSeriesStatus.cs @@ -5,7 +5,7 @@ namespace MalApi { - public enum MalSeriesStatus + public enum MalAnimeSeriesStatus { Airing = 1, FinishedAiring = 2, diff --git a/MalApi/MalAnimeType.cs b/MalApi/Anime/MalAnimeType.cs similarity index 100% rename from MalApi/MalAnimeType.cs rename to MalApi/Anime/MalAnimeType.cs diff --git a/MalApi/MyAnimeListEntry.cs b/MalApi/Anime/MyAnimeListEntry.cs similarity index 91% rename from MalApi/MyAnimeListEntry.cs rename to MalApi/Anime/MyAnimeListEntry.cs index e4228d1..2482b22 100644 --- a/MalApi/MyAnimeListEntry.cs +++ b/MalApi/Anime/MyAnimeListEntry.cs @@ -8,7 +8,7 @@ namespace MalApi public class MyAnimeListEntry : IEquatable { public decimal? Score { get; private set; } - public CompletionStatus Status { get; private set; } + public AnimeCompletionStatus Status { get; private set; } public int NumEpisodesWatched { get; private set; } public UncertainDate MyStartDate { get; private set; } public UncertainDate MyFinishDate { get; private set; } @@ -16,7 +16,7 @@ public class MyAnimeListEntry : IEquatable public MalAnimeInfoFromUserLookup AnimeInfo { get; private set; } public ICollection Tags { get; private set; } - public MyAnimeListEntry(decimal? score, CompletionStatus status, int numEpisodesWatched, UncertainDate myStartDate, + public MyAnimeListEntry(decimal? score, AnimeCompletionStatus status, int numEpisodesWatched, UncertainDate myStartDate, UncertainDate myFinishDate, DateTime myLastUpdate, MalAnimeInfoFromUserLookup animeInfo, ICollection tags) { Score = score; diff --git a/MalApi/MalApi.csproj b/MalApi/MalApi.csproj index 43e369b..072534c 100644 --- a/MalApi/MalApi.csproj +++ b/MalApi/MalApi.csproj @@ -43,5 +43,6 @@ + \ No newline at end of file diff --git a/MalApi/MalAppInfoXml.cs b/MalApi/MalAppInfoXml.cs index 71d055c..125e55e 100644 --- a/MalApi/MalAppInfoXml.cs +++ b/MalApi/MalAppInfoXml.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Text; using System.IO; using System.Xml.Linq; using System.Text.RegularExpressions; @@ -31,7 +30,8 @@ public static async Task ParseAsync(TextReader xmlTextRead // No async version of XDocument.Load in the full framework yet. // StringReader won't block though. XDocument doc = XDocument.Load(sanitizedXmlTextReader); - return Parse(doc); + + return ParseResults(doc); } } @@ -104,7 +104,7 @@ private static async Task SanitizeAnimeListXmlAsync(TextReader xml /// /// /// - public static MalUserLookupResults Parse(XDocument doc) + public static MalUserLookupResults ParseResults(XDocument doc) { Logging.Log.Trace("Parsing XML."); @@ -127,69 +127,142 @@ public static MalUserLookupResults Parse(XDocument doc) int userId = GetElementValueInt(myinfo, "user_id"); string canonicalUserName = GetElementValueString(myinfo, "user_name"); - List entries = new List(); + List animeEntries = new List(); + List mangaEntries = new List(); - IEnumerable animes = doc.Root.Elements("anime"); - foreach (XElement anime in animes) + // Anime entries + if (doc.Root.Element("anime") != null) { - int animeId = GetElementValueInt(anime, "series_animedb_id"); - string title = GetElementValueString(anime, "series_title"); + IEnumerable animes = doc.Root.Elements("anime"); + foreach (XElement anime in animes) + { + int animeId = GetElementValueInt(anime, "series_animedb_id"); + string title = GetElementValueString(anime, "series_title"); - string synonymList = GetElementValueString(anime, "series_synonyms"); - string[] rawSynonyms = synonymList.Split(SynonymSeparator, StringSplitOptions.RemoveEmptyEntries); + string synonymList = GetElementValueString(anime, "series_synonyms"); + string[] rawSynonyms = synonymList.Split(SynonymSeparator, StringSplitOptions.RemoveEmptyEntries); - // filter out synonyms that are the same as the main title - HashSet synonyms = new HashSet(rawSynonyms.Where(synonym => !synonym.Equals(title, StringComparison.Ordinal))); + // filter out synonyms that are the same as the main title + HashSet synonyms = new HashSet(rawSynonyms.Where(synonym => !synonym.Equals(title, StringComparison.Ordinal))); - int seriesTypeInt = GetElementValueInt(anime, "series_type"); - MalAnimeType seriesType = (MalAnimeType)seriesTypeInt; + int seriesTypeInt = GetElementValueInt(anime, "series_type"); + MalAnimeType seriesType = (MalAnimeType)seriesTypeInt; - int numEpisodes = GetElementValueInt(anime, "series_episodes"); + int numEpisodes = GetElementValueInt(anime, "series_episodes"); - int seriesStatusInt = GetElementValueInt(anime, "series_status"); - MalSeriesStatus seriesStatus = (MalSeriesStatus)seriesStatusInt; + int seriesStatusInt = GetElementValueInt(anime, "series_status"); + MalAnimeSeriesStatus seriesStatus = (MalAnimeSeriesStatus)seriesStatusInt; - string seriesStartString = GetElementValueString(anime, "series_start"); - UncertainDate seriesStart = UncertainDate.FromMalDateString(seriesStartString); + string seriesStartString = GetElementValueString(anime, "series_start"); + UncertainDate seriesStart = UncertainDate.FromMalDateString(seriesStartString); - string seriesEndString = GetElementValueString(anime, "series_end"); - UncertainDate seriesEnd = UncertainDate.FromMalDateString(seriesEndString); + string seriesEndString = GetElementValueString(anime, "series_end"); + UncertainDate seriesEnd = UncertainDate.FromMalDateString(seriesEndString); - string seriesImage = GetElementValueString(anime, "series_image"); + string seriesImage = GetElementValueString(anime, "series_image"); - MalAnimeInfoFromUserLookup animeInfo = new MalAnimeInfoFromUserLookup(animeId: animeId, title: title, - type: seriesType, synonyms: synonyms, status: seriesStatus, numEpisodes: numEpisodes, startDate: seriesStart, - endDate: seriesEnd, imageUrl: seriesImage); + MalAnimeInfoFromUserLookup animeInfo = new MalAnimeInfoFromUserLookup(animeId: animeId, title: title, + type: seriesType, synonyms: synonyms, status: seriesStatus, numEpisodes: numEpisodes, startDate: seriesStart, + endDate: seriesEnd, imageUrl: seriesImage); - int numEpisodesWatched = GetElementValueInt(anime, "my_watched_episodes"); + int numEpisodesWatched = GetElementValueInt(anime, "my_watched_episodes"); - string myStartDateString = GetElementValueString(anime, "my_start_date"); - UncertainDate myStartDate = UncertainDate.FromMalDateString(myStartDateString); + string myStartDateString = GetElementValueString(anime, "my_start_date"); + UncertainDate myStartDate = UncertainDate.FromMalDateString(myStartDateString); - string myFinishDateString = GetElementValueString(anime, "my_finish_date"); - UncertainDate myFinishDate = UncertainDate.FromMalDateString(myFinishDateString); + string myFinishDateString = GetElementValueString(anime, "my_finish_date"); + UncertainDate myFinishDate = UncertainDate.FromMalDateString(myFinishDateString); - decimal rawScore = GetElementValueDecimal(anime, "my_score"); - decimal? myScore = rawScore == 0 ? (decimal?)null : rawScore; + decimal rawScore = GetElementValueDecimal(anime, "my_score"); + decimal? myScore = rawScore == 0 ? (decimal?)null : rawScore; - int completionStatusInt = GetElementValueInt(anime, "my_status"); - CompletionStatus completionStatus = (CompletionStatus)completionStatusInt; + int completionStatusInt = GetElementValueInt(anime, "my_status"); + AnimeCompletionStatus completionStatus = (AnimeCompletionStatus)completionStatusInt; - long lastUpdatedUnixTimestamp = GetElementValueLong(anime, "my_last_updated"); - DateTime lastUpdated = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + TimeSpan.FromSeconds(lastUpdatedUnixTimestamp); + long lastUpdatedUnixTimestamp = GetElementValueLong(anime, "my_last_updated"); + DateTime lastUpdated = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + TimeSpan.FromSeconds(lastUpdatedUnixTimestamp); - string rawTagsString = GetElementValueString(anime, "my_tags"); - string[] untrimmedTags = rawTagsString.Split(TagSeparator, StringSplitOptions.RemoveEmptyEntries); - List tags = new List(untrimmedTags.Select(tag => tag.Trim())); + string rawTagsString = GetElementValueString(anime, "my_tags"); + string[] untrimmedTags = rawTagsString.Split(TagSeparator, StringSplitOptions.RemoveEmptyEntries); + List tags = new List(untrimmedTags.Select(tag => tag.Trim())); - MyAnimeListEntry entry = new MyAnimeListEntry(score: myScore, status: completionStatus, numEpisodesWatched: numEpisodesWatched, - myStartDate: myStartDate, myFinishDate: myFinishDate, myLastUpdate: lastUpdated, animeInfo: animeInfo, tags: tags); + MyAnimeListEntry entry = new MyAnimeListEntry(score: myScore, status: completionStatus, numEpisodesWatched: numEpisodesWatched, + myStartDate: myStartDate, myFinishDate: myFinishDate, myLastUpdate: lastUpdated, animeInfo: animeInfo, tags: tags); - entries.Add(entry); + animeEntries.Add(entry); + } } - MalUserLookupResults results = new MalUserLookupResults(userId: userId, canonicalUserName: canonicalUserName, animeList: entries); + // Manga entries + if (doc.Root.Element("manga") != null) + { + + IEnumerable mangas = doc.Root.Elements("manga"); + foreach (XElement manga in mangas) + { + int mangaId = GetElementValueInt(manga, "series_mangadb_id"); + string title = GetElementValueString(manga, "series_title"); + + string synonymList = GetElementValueString(manga, "series_synonyms"); + string[] rawSynonyms = synonymList.Split(SynonymSeparator, StringSplitOptions.RemoveEmptyEntries); + + // filter out synonyms that are the same as the main title + HashSet synonyms = new HashSet(rawSynonyms.Where(synonym => !synonym.Equals(title, StringComparison.Ordinal))); + + int seriesTypeInt = GetElementValueInt(manga, "series_type"); + MalMangaType seriesType = (MalMangaType)seriesTypeInt; + + int numChapters = GetElementValueInt(manga, "series_chapters"); + + int numVolumes = GetElementValueInt(manga, "series_volumes"); + + int seriesStatusInt = GetElementValueInt(manga, "series_status"); + MalMangaSeriesStatus seriesStatus = (MalMangaSeriesStatus)seriesStatusInt; + + string seriesStartString = GetElementValueString(manga, "series_start"); + UncertainDate seriesStart = UncertainDate.FromMalDateString(seriesStartString); + + string seriesEndString = GetElementValueString(manga, "series_end"); + UncertainDate seriesEnd = UncertainDate.FromMalDateString(seriesEndString); + + string seriesImage = GetElementValueString(manga, "series_image"); + + MalMangaInfoFromUserLookup mangaInfo = new MalMangaInfoFromUserLookup(mangaId: mangaId, title: title, + type: seriesType, synonyms: synonyms, status: seriesStatus, numChapters: numChapters, numVolumes: numVolumes, startDate: seriesStart, + endDate: seriesEnd, imageUrl: seriesImage); + + + int numChaptersRead = GetElementValueInt(manga, "my_read_chapters"); + + int numVolumesRead = GetElementValueInt(manga, "my_read_volumes"); + + string myStartDateString = GetElementValueString(manga, "my_start_date"); + UncertainDate myStartDate = UncertainDate.FromMalDateString(myStartDateString); + + string myFinishDateString = GetElementValueString(manga, "my_finish_date"); + UncertainDate myFinishDate = UncertainDate.FromMalDateString(myFinishDateString); + + decimal rawScore = GetElementValueDecimal(manga, "my_score"); + decimal? myScore = rawScore == 0 ? (decimal?)null : rawScore; + + int completionStatusInt = GetElementValueInt(manga, "my_status"); + MangaCompletionStatus completionStatus = (MangaCompletionStatus)completionStatusInt; + + long lastUpdatedUnixTimestamp = GetElementValueLong(manga, "my_last_updated"); + DateTime lastUpdated = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc) + TimeSpan.FromSeconds(lastUpdatedUnixTimestamp); + + string rawTagsString = GetElementValueString(manga, "my_tags"); + string[] untrimmedTags = rawTagsString.Split(TagSeparator, StringSplitOptions.RemoveEmptyEntries); + List tags = new List(untrimmedTags.Select(tag => tag.Trim())); + + MyMangaListEntry entry = new MyMangaListEntry(score: myScore, status: completionStatus, numChaptersRead: numChaptersRead, numVolumesRead: numVolumesRead, + myStartDate: myStartDate, myFinishDate: myFinishDate, myLastUpdate: lastUpdated, mangaInfo: mangaInfo, tags: tags); + + mangaEntries.Add(entry); + } + } + MalUserLookupResults results = new MalUserLookupResults(userId: userId, canonicalUserName: canonicalUserName, animeList: animeEntries, mangaList: mangaEntries); Logging.Log.Trace("Parsed XML."); return results; } diff --git a/MalApi/MalUserLookupResults.cs b/MalApi/MalUserLookupResults.cs index 0edc1ce..dff2fe7 100644 --- a/MalApi/MalUserLookupResults.cs +++ b/MalApi/MalUserLookupResults.cs @@ -8,6 +8,7 @@ namespace MalApi public class MalUserLookupResults { public ICollection AnimeList { get; private set; } + public ICollection MangaList { get; private set; } public int UserId { get; private set; } /// @@ -15,12 +16,21 @@ public class MalUserLookupResults /// public string CanonicalUserName { get; private set; } + // Left for backwards compatibility public MalUserLookupResults(int userId, string canonicalUserName, ICollection animeList) { UserId = userId; CanonicalUserName = canonicalUserName; AnimeList = animeList; } + + public MalUserLookupResults(int userId, string canonicalUserName, ICollection animeList, ICollection mangaList) + { + UserId = userId; + CanonicalUserName = canonicalUserName; + AnimeList = animeList; + MangaList = mangaList; + } } } diff --git a/MalApi/Manga/MalMangaInfoFromUserLookup.cs b/MalApi/Manga/MalMangaInfoFromUserLookup.cs new file mode 100644 index 0000000..5f5c3d2 --- /dev/null +++ b/MalApi/Manga/MalMangaInfoFromUserLookup.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MalApi +{ + public class MalMangaInfoFromUserLookup : IEquatable + { + public int MangaId { get; private set; } + public string Title { get; private set; } + + /// + /// Could be something other than the enumerated values if MAL adds new types! + /// + public MalMangaType Type { get; private set; } + + public ICollection Synonyms { get; private set; } + public MalMangaSeriesStatus Status { get; private set; } + + public int NumChapters { get; private set; } + + public int NumVolumes { get; private set; } + + public UncertainDate StartDate { get; private set; } + public UncertainDate EndDate { get; private set; } + public string ImageUrl { get; private set; } + + public MalMangaInfoFromUserLookup(int mangaId, string title, MalMangaType type, ICollection synonyms, MalMangaSeriesStatus status, + int numChapters, int numVolumes, UncertainDate startDate, UncertainDate endDate, string imageUrl) + { + MangaId = mangaId; + Title = title; + Type = type; + Synonyms = synonyms; + Status = status; + NumChapters = numChapters; + NumVolumes = numVolumes; + StartDate = startDate; + EndDate = endDate; + ImageUrl = imageUrl; + } + + public override bool Equals(object obj) + { + return Equals(obj as MalMangaInfoFromUserLookup); + } + + public bool Equals(MalMangaInfoFromUserLookup other) + { + if (other == null) return false; + return this.MangaId == other.MangaId; + } + + public override int GetHashCode() + { + return MangaId.GetHashCode(); + } + + public override string ToString() + { + return Title; + } + } +} diff --git a/MalApi/Manga/MalMangaSeriesStatus.cs b/MalApi/Manga/MalMangaSeriesStatus.cs new file mode 100644 index 0000000..7eb27a7 --- /dev/null +++ b/MalApi/Manga/MalMangaSeriesStatus.cs @@ -0,0 +1,9 @@ +namespace MalApi +{ + public enum MalMangaSeriesStatus + { + Publishing = 1, + Finished = 2, + NotYetPublished = 3 + } +} \ No newline at end of file diff --git a/MalApi/Manga/MalMangaType.cs b/MalApi/Manga/MalMangaType.cs new file mode 100644 index 0000000..43bbe46 --- /dev/null +++ b/MalApi/Manga/MalMangaType.cs @@ -0,0 +1,14 @@ +namespace MalApi +{ + public enum MalMangaType + { + Unknown = 0, + Manga = 1, + Novel = 2, + OneShot = 3, + Doujin = 4, + Manwha = 5, + Manhua = 6, + OEL = 7 + } +} \ No newline at end of file diff --git a/MalApi/Manga/MangaCompletionStatus.cs b/MalApi/Manga/MangaCompletionStatus.cs new file mode 100644 index 0000000..16af162 --- /dev/null +++ b/MalApi/Manga/MangaCompletionStatus.cs @@ -0,0 +1,19 @@ +using System.Xml.Serialization; + +namespace MalApi +{ + // XML enum attributes are needed for proper serialization upon sending an update request + public enum MangaCompletionStatus + { + [XmlEnum(Name = "reading")] + Reading = 1, + [XmlEnum(Name = "completed")] + Completed = 2, + [XmlEnum(Name = "onhold")] + OnHold = 3, + [XmlEnum(Name = "dropped")] + Dropped = 4, + [XmlEnum(Name = "plantoread")] + PlanToRead = 6, + } +} \ No newline at end of file diff --git a/MalApi/Manga/MangaUpdate.cs b/MalApi/Manga/MangaUpdate.cs new file mode 100644 index 0000000..173bef6 --- /dev/null +++ b/MalApi/Manga/MangaUpdate.cs @@ -0,0 +1,121 @@ +using System; +using System.Xml; +using System.Xml.Linq; +using System.Xml.Schema; +using System.Xml.Serialization; + +namespace MalApi +{ + /// + /// The update object sent to MAL when updating a manga entry. + /// Only specified values will be changed. The rest will remain unchanged. + /// More details: https://myanimelist.net/modules.php?go=api#mangavalues + /// + [XmlRoot("entry")] + public class MangaUpdate + { + [XmlElement("chapter")] + public int? Chapter { get; set; } = null; + + [XmlElement("volume")] + public int? Volume { get; set; } = null; + + [XmlElement("status")] + public MangaCompletionStatus? Status { get; set; } = null; + + [XmlElement("score")] + public int? Score { get; set; } = null; + + [XmlElement("times_reread")] + public int? TimesReread { get; set; } = null; + + [XmlElement("reread_value")] + public int? RereadValue { get; set; } = null; + + [XmlIgnore] + public DateTime? DateStart { get; set; } = null; + + [XmlElement("date_start")] + private string FormattedDateStart + { + get + { + return DateStart?.ToString("MMddyyyy"); + } + set + { + DateStart = DateTime.Parse(value); + } + } + + public DateTime? DateFinish { get; set; } = null; + + [XmlElement("date_finish")] + private string FormattedDateFinish + { + get + { + return DateFinish?.ToString("MMddyyyy"); + } + set + { + DateFinish = DateTime.Parse(value); + } + } + + [XmlElement("priority")] + public int? Priority { get; set; } = null; + + [XmlElement("enable_discussion")] + public int? EnableDiscussion { get; set; } = null; + + [XmlElement("enable_rereading")] + public int? EnableRereading { get; set; } = null; + + [XmlElement("comments")] + public string Comments { get; set; } = null; + + [XmlElement("scan_group")] + public string ScanGroup { get; set; } = null; + + [XmlElement("tags")] + public string Tags { get; set; } = null; + + [XmlElement("retail_volumes")] + public int? RetailVolumes { get; set; } = null; + + /// + /// Generates an XML with the current information stored in the object. XML can later be used to update records on MAL. + /// + /// String representation of object-generated XML. + public string GenerateXml() + { + XDocument document = new XDocument( + new XDeclaration("1.0", "UTF-8", null), + new XElement("entry", + Chapter != null ? new XElement("chapter", Chapter) : null, + Volume != null ? new XElement("volume", Volume) : null, + Status != null ? new XElement("status", (int?)Status) : null, + Score != null ? new XElement("score", Score) : null, + TimesReread != null ? new XElement("times_reread", TimesReread) : null, + RereadValue != null ? new XElement("reread_value", RereadValue) : null, + FormattedDateStart != null ? new XElement("date_start", FormattedDateStart) : null, + FormattedDateFinish != null ? new XElement("date_finish", FormattedDateFinish) : null, + Priority != null ? new XElement("priority", Priority) : null, + EnableDiscussion != null ? new XElement("enable_discussion", EnableDiscussion) : null, + EnableRereading != null ? new XElement("enable_rereading", EnableRereading) : null, + Comments != null ? new XElement("comments", Comments) : null, + // ScanGroup != null ? new XElement("scan_group", ScanGroup) : null, + Tags != null ? new XElement("tags", Tags) : null, + RetailVolumes != null ? new XElement("retail_volumes", RetailVolumes) : null + ) + ); + + using (Utf8StringWriter writer = new Utf8StringWriter()) + { + document.Save(writer); + return writer.ToString(); + } + } + } +} diff --git a/MalApi/Manga/MyMangaListEntry.cs b/MalApi/Manga/MyMangaListEntry.cs new file mode 100644 index 0000000..40d41a4 --- /dev/null +++ b/MalApi/Manga/MyMangaListEntry.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MalApi +{ + public class MyMangaListEntry : IEquatable + { + public decimal? Score { get; private set; } + public MangaCompletionStatus Status { get; private set; } + public int NumChaptersRead { get; private set; } + public int NumVolumesRead { get; private set; } + public UncertainDate MyStartDate { get; private set; } + public UncertainDate MyFinishDate { get; private set; } + public DateTime MyLastUpdate { get; private set; } + public MalMangaInfoFromUserLookup MangaInfo { get; private set; } + public ICollection Tags { get; private set; } + + public MyMangaListEntry(decimal? score, MangaCompletionStatus status, int numChaptersRead, int numVolumesRead, UncertainDate myStartDate, + UncertainDate myFinishDate, DateTime myLastUpdate, MalMangaInfoFromUserLookup mangaInfo, ICollection tags) + { + Score = score; + Status = status; + NumChaptersRead = numChaptersRead; + NumVolumesRead = numVolumesRead; + MyStartDate = myStartDate; + MyFinishDate = myFinishDate; + MyLastUpdate = myLastUpdate; + MangaInfo = mangaInfo; + Tags = tags; + } + + public bool Equals(MyMangaListEntry other) + { + if (other == null) return false; + return this.MangaInfo.MangaId == other.MangaInfo.MangaId; + } + + public override bool Equals(object obj) + { + return Equals(obj as MyMangaListEntry); + } + + public override int GetHashCode() + { + return MangaInfo.MangaId.GetHashCode(); + } + + public override string ToString() + { + return MangaInfo.Title; + } + } +} diff --git a/MalApi/MyAnimeListApi.cs b/MalApi/MyAnimeListApi.cs index f257f97..f46aefc 100644 --- a/MalApi/MyAnimeListApi.cs +++ b/MalApi/MyAnimeListApi.cs @@ -1,11 +1,11 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; using System.Net; using System.IO; using System.Text.RegularExpressions; using System.Net.Http; +using System.Net.Http.Headers; using System.Threading.Tasks; using System.Threading; @@ -16,7 +16,8 @@ namespace MalApi /// public class MyAnimeListApi : IMyAnimeListApi { - private const string MalAppInfoUri = "https://myanimelist.net/malappinfo.php?status=all&type=anime"; + private static readonly string AnimeDetailsUrlFormat = "https://myanimelist.net/anime/{0}"; + private const string RecentOnlineUsersUri = "https://myanimelist.net/users.php"; /// @@ -51,7 +52,7 @@ public MyAnimeListApi() m_httpHandler = new HttpClientHandler() { AllowAutoRedirect = true, - UseCookies = false, + UseCookies = true, // Very important optimization! Time to get an anime list of ~150 entries 2.6s -> 0.7s AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate, @@ -72,6 +73,34 @@ private HttpRequestMessage InitNewRequest(string uri, HttpMethod method) return request; } + private HttpRequestMessage InitNewRequestWithCredentials(string uri, HttpMethod method, string user, string password) + { + HttpRequestMessage request = InitNewRequest(uri, method); + + // Requests with credentials require them to be encoded in base64 + string credentials; + if (user != null && password != null) + { + byte[] plainTextBytes = Encoding.UTF8.GetBytes(user + ":" + password); + credentials = Convert.ToBase64String(plainTextBytes); + } + else + { + if (user == null) + { + throw new ArgumentNullException(nameof(user)); + } + + // This will always be true given the && condition above + throw new ArgumentNullException(nameof(password)); + } + + // Adding the authorization header with the credentials + request.Headers.Authorization = new AuthenticationHeaderValue("Basic", credentials); + + return request; + } + private Task ProcessRequestAsync(HttpRequestMessage request, Func processingFunc, string baseErrorMessage, CancellationToken cancellationToken) { return ProcessRequestAsync(request, (string html, object dummy) => processingFunc(html), (object)null, @@ -181,6 +210,44 @@ public Task GetAnimeListForUserAsync(string user) return GetAnimeListForUserAsync(user, CancellationToken.None); } + /// + /// Updates a user's anime list entry. + /// + /// ID of the anime + /// The updated information + /// + /// + /// + public Task UpdateAnimeForUserAsync(int animeId, AnimeUpdate updateInfo, string user, string password) + { + return UpdateAnimeForUserAsync(animeId, updateInfo, user, password, CancellationToken.None); + } + + /// + /// Gets a user's manga list. This method requires a MAL API key. + /// + /// + /// + /// + /// + public Task GetMangaListForUserAsync(string user) + { + return GetMangaListForUserAsync(user, CancellationToken.None); + } + + /// + /// Updates a user's manga list entry. + /// + /// ID of the manga + /// The updated information + /// + /// + /// + public Task UpdateMangaForUserAsync(int mangaId, MangaUpdate updateInfo, string user, string password) + { + return UpdateMangaForUserAsync(mangaId, updateInfo, user, password, CancellationToken.None); + } + /// /// Gets a user's anime list. This method requires a MAL API key. /// @@ -191,7 +258,9 @@ public Task GetAnimeListForUserAsync(string user) /// public async Task GetAnimeListForUserAsync(string user, CancellationToken cancellationToken) { - string userInfoUri = MalAppInfoUri + "&u=" + Uri.EscapeDataString(user); + const string malAppAnimeInfoUriFormatString = "https://myanimelist.net/malappinfo.php?status=all&type=anime&u={0}"; + + string userInfoUri = string.Format(malAppAnimeInfoUriFormatString, Uri.EscapeDataString(user)); Logging.Log.InfoFormat("Getting anime list for MAL user {0} using URI {1}", user, userInfoUri); @@ -213,8 +282,10 @@ public async Task GetAnimeListForUserAsync(string user, Ca try { HttpRequestMessage request = InitNewRequest(userInfoUri, HttpMethod.Get); - MalUserLookupResults parsedList = await ProcessRequestAsync(request, responseProcessingFunc, cancellationToken: cancellationToken, - baseErrorMessage: string.Format("Failed getting anime list for user {0} using url {1}", user, userInfoUri)).ConfigureAwait(continueOnCapturedContext: false); + MalUserLookupResults parsedList = await ProcessRequestAsync(request, responseProcessingFunc, + cancellationToken: cancellationToken, + baseErrorMessage: string.Format("Failed getting anime list for user {0} using url {1}", user, + userInfoUri)).ConfigureAwait(continueOnCapturedContext: false); Logging.Log.InfoFormat("Successfully retrieved anime list for user {0}", user); return parsedList; @@ -226,6 +297,144 @@ public async Task GetAnimeListForUserAsync(string user, Ca } } + /// + /// Updates a user's anime list entry. + /// + /// ID of the anime + /// The updated information + /// + /// MAL username + /// MAL password + /// + public async Task UpdateAnimeForUserAsync(int animeId, AnimeUpdate updateInfo, string user, string password, CancellationToken cancellationToken) + { + const string malAnimeUpdateUriFormatString = "https://myanimelist.net/api/animelist/update/{0}.xml"; + + string userInfoUri = string.Format(malAnimeUpdateUriFormatString, Uri.EscapeDataString(animeId.ToString())); + + Logging.Log.InfoFormat("Updating anime entry for MAL anime ID {0}, user {1} using URI {2}", animeId, user, userInfoUri); + + Func responseProcessingFunc = (response) => + { + return response; + }; + + try + { + HttpRequestMessage request = InitNewRequestWithCredentials(userInfoUri, HttpMethod.Post, user, password); + + // Encoding and adding the new information in the content(body) of the request + string xml = updateInfo.GenerateXml(); + request.Content = new FormUrlEncodedContent(new KeyValuePair[] { new KeyValuePair("data", xml) }); + + string result = await ProcessRequestAsync(request, responseProcessingFunc, cancellationToken: cancellationToken, + baseErrorMessage: string.Format("Failed updating anime entry for anime ID {0}, user {1} using url {2}", animeId, user, userInfoUri)).ConfigureAwait(continueOnCapturedContext: false); + + Logging.Log.InfoFormat("Successfully updated anime entry for anime ID {0} and user {1}", animeId, user); + + return result; + } + catch (OperationCanceledException) + { + Logging.Log.InfoFormat("Canceled updating anime entry for MAL anime ID {0} and user {1}", animeId, user); + throw; + } + } + + /// + /// Gets a user's manga list. This method requires a MAL API key. + /// + /// + /// + /// + /// + /// + public async Task GetMangaListForUserAsync(string user, + CancellationToken cancellationToken) + { + const string malAppMangaInfoUriFormatString = "https://myanimelist.net/malappinfo.php?status=all&type=manga&u={0}"; + + string userInfoUri = string.Format(malAppMangaInfoUriFormatString, Uri.EscapeDataString(user)); + + Logging.Log.InfoFormat("Getting manga list for MAL user {0} using URI {1}", user, userInfoUri); + + Func responseProcessingFunc = (xml) => + { + using (TextReader xmlTextReader = new StringReader(xml)) + { + try + { + return MalAppInfoXml.Parse(xmlTextReader); + } + catch (MalUserNotFoundException ex) + { + throw new MalUserNotFoundException(string.Format("No MAL list exists for {0}.", user), ex); + } + } + }; + + try + { + HttpRequestMessage request = InitNewRequest(userInfoUri, HttpMethod.Get); + MalUserLookupResults parsedList = await ProcessRequestAsync(request, responseProcessingFunc, + cancellationToken: cancellationToken, + baseErrorMessage: string.Format("Failed getting manga list for user {0} using url {1}", user, + userInfoUri)).ConfigureAwait(continueOnCapturedContext: false); + + Logging.Log.InfoFormat("Successfully retrieved manga list for user {0}", user); + return parsedList; + } + catch (OperationCanceledException) + { + Logging.Log.InfoFormat("Canceled getting manga list for MAL user {0}", user); + throw; + } + } + + /// + /// Updates a user's manga list entry. + /// + /// ID of the manga + /// The updated information + /// + /// MAL user + /// MAL password + /// + public async Task UpdateMangaForUserAsync(int mangaId, MangaUpdate updateInfo, string user, string password, CancellationToken cancellationToken) + { + const string malMangaUpdateUriFormatString = "https://myanimelist.net/api/mangalist/update/{0}.xml"; + + string userInfoUri = string.Format(malMangaUpdateUriFormatString, Uri.EscapeDataString(mangaId.ToString())); + + Logging.Log.InfoFormat("Updating manga entry for MAL manga ID {0}, user {1} using URI {2}", mangaId, user, userInfoUri); + + Func responseProcessingFunc = (response) => + { + return response; + }; + + try + { + HttpRequestMessage request = InitNewRequestWithCredentials(userInfoUri, HttpMethod.Post, user, password); + + // Encoding and adding the new information in the content(body) of the request + string xml = updateInfo.GenerateXml(); + request.Content = new FormUrlEncodedContent(new KeyValuePair[] { new KeyValuePair("data", xml) }); + + string result = await ProcessRequestAsync(request, responseProcessingFunc, cancellationToken: cancellationToken, + baseErrorMessage: string.Format("Failed updating manga entry for manga ID {0}, user {1} using url {2}", mangaId, user, userInfoUri)).ConfigureAwait(continueOnCapturedContext: false); + + Logging.Log.InfoFormat("Successfully updated manga entry for manga ID {0} and user {1}", mangaId, user); + + return result; + } + catch (OperationCanceledException) + { + Logging.Log.InfoFormat("Canceled updating manga entry for MAL manga ID {0} and user {1}", mangaId, user); + throw; + } + } + /// /// Gets a user's anime list. This method requires a MAL API key. /// @@ -238,6 +447,46 @@ public MalUserLookupResults GetAnimeListForUser(string user) return GetAnimeListForUserAsync(user).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); } + /// + /// Updates a user's anime entry. Required username and password or a base64 encrypted username and password. + /// + /// ID of the updated anime + /// Required data to update the anime + /// Username + /// Password + /// base64 encrypted username and password + /// + public string UpdateAnimeForUser(int animeId, AnimeUpdate updateInfo, string user, string password) + { + return UpdateAnimeForUserAsync(animeId, updateInfo, user, password).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); + } + + /// + /// Gets a user's manga list. This method requires a MAL API key. + /// + /// + /// + /// + /// + public MalUserLookupResults GetMangaListForUser(string user) + { + return GetMangaListForUserAsync(user).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); + } + + /// + /// Updates a user's manga entry. Required username and password or a base64 encrypted username and password. + /// + /// ID of the updated manga + /// Required data to update the manga + /// Username + /// Password + /// base64 encrypted username and password + /// + public string UpdateMangaForUser(int mangaId, MangaUpdate updateInfo, string user, string password) + { + return UpdateMangaForUserAsync(mangaId, updateInfo, user, password).ConfigureAwait(continueOnCapturedContext: false).GetAwaiter().GetResult(); + } + private static Lazy s_recentOnlineUsersRegex = new Lazy(() => new Regex("/profile/(?[^\"]+)\">\\k", RegexOptions.Compiled | RegexOptions.CultureInvariant | RegexOptions.IgnoreCase)); @@ -306,7 +555,6 @@ private RecentUsersResults ScrapeUsersFromHtml(string recentUsersHtml) return new RecentUsersResults(users); } - private static readonly string AnimeDetailsUrlFormat = "https://myanimelist.net/anime/{0}"; private static Lazy s_animeDetailsRegex = new Lazy(() => new Regex( @"Genres:\s*?(?:\d+)/[^""]+?""[^>]*?>(?.*?)(?:, )?)*", RegexOptions.Compiled)); @@ -345,6 +593,7 @@ public async Task GetAnimeDetailsAsync(int animeId, Cancell httpErrorStatusHandler: GetAnimeDetailsHttpErrorStatusHandler, cancellationToken: cancellationToken, baseErrorMessage: string.Format("Failed getting anime details for anime ID {0}.", animeId)) .ConfigureAwait(continueOnCapturedContext: false); + Logging.Log.InfoFormat("Successfully got details from {0}.", url); return results; } diff --git a/MalApi/Utf8StringWriter.cs b/MalApi/Utf8StringWriter.cs new file mode 100644 index 0000000..6d42349 --- /dev/null +++ b/MalApi/Utf8StringWriter.cs @@ -0,0 +1,13 @@ +using System.IO; +using System.Text; + +namespace MalApi +{ + /// + /// Used to override the encoding of the writer to UTF-8. + /// + public class Utf8StringWriter : StringWriter + { + public override Encoding Encoding => Encoding.UTF8; + } +}