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 Anime ListManga ListQuick AddList Settingsnaps250ProfileFriendsClubsBlog PostsReviewsRecommendationsAccount SettingsLogoutMyAnimeList.net AllAnimeMangaCharactersPeopleNewsFeatured ArticlesForumClubsUsers View all results for ${keyword} Anime Anime SearchTop AnimeSeasonal AnimeVideosReviewsRecommendations2017 Challenge Manga Manga SearchTop MangaReviewsRecommendations2017 Challenge Community ForumsClubsBlogsUsersDiscord Chat Industry NewsFeatured ArticlesPeopleCharacters Watch Episode VideosPromotional Videos Help AboutSupportAdvertisingFAQReportStaffMAL Supporter Edit Anime Edit Anime Anime Title Cowboy Bebop Status WatchingCompletedOn-HoldDroppedPlan to Watch Re-watching Episodes Watched + / 26 History Your Score Select score(10) Masterpiece(9) Great(8) Very Good(7) Good(6) Fine(5) Average(4) Bad(3) Very Bad(2) Horrible(1) Appalling Start Date Month: JanFebMarAprMayJunJulAugSepOctNovDec Day: 12345678910111213141516171819202122232425262728293031 Year: 2017201620152014201320122011201020092008200720062005200420032002200120001999199819971996199519941993199219911990198919881987 Insert Today Unknown Date Finish Date Month: JanFebMarAprMayJunJulAugSepOctNovDec Day: 12345678910111213141516171819202122232425262728293031 Year: 2017201620152014201320122011201020092008200720062005200420032002200120001999199819971996199519941993199219911990198919881987 Insert Today Unknown Date Hide Advanced Show Advanced Tags test updated tag, test updated tag 2 Priority LowMediumHigh Storage Select storage typeHard DriveExternal HDNASBlu-rayDVD / CDRetail DVDVHSNone Total DvD's Total TimesRe-watched Series Rewatch Value Select rewatch valueVery LowLowMediumHighVery High Comments test updated comment, test updated comment2 Ask to Discuss? Ask to discuss an episode after you update your episode countDon't ask to discuss Post to SNS Follow default settingPost with confirmationPost every time (without confirmation)Do not post \ 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 Anime ListManga ListQuick AddList Settingsnaps250ProfileFriendsClubsBlog PostsReviewsRecommendationsAccount SettingsLogoutMyAnimeList.net AllAnimeMangaCharactersPeopleNewsFeatured ArticlesForumClubsUsers View all results for ${keyword} Anime Anime SearchTop AnimeSeasonal AnimeVideosReviewsRecommendations2018 Challenge Manga Manga SearchTop MangaReviewsRecommendations2018 Challenge Community ForumsClubsBlogsUsersDiscord Chat Industry NewsFeatured ArticlesPeopleCharacters Watch Episode VideosPromotional Videos Help AboutSupportAdvertisingFAQReportStaffMAL Supporter Edit Manga Edit Manga Manga Title Monster Status ReadingCompletedOn-HoldDroppedPlan to Read Re-reading Volumes Read + / 18 Chapters Read + / 162 History Your Score Select score(10) Masterpiece(9) Great(8) Very Good(7) Good(6) Fine(5) Average(4) Bad(3) Very Bad(2) Horrible(1) Appalling Start Date Month: JanFebMarAprMayJunJulAugSepOctNovDec Day: 12345678910111213141516171819202122232425262728293031 Year: 2018201720162015201420132012201120102009200820072006200520042003200220012000199919981997199619951994199319921991199019891988 Insert Today Unknown Date Finish Date Month: JanFebMarAprMayJunJulAugSepOctNovDec Day: 12345678910111213141516171819202122232425262728293031 Year: 2018201720162015201420132012201120102009200820072006200520042003200220012000199919981997199619951994199319921991199019891988 Insert Today Unknown Date Hide Advanced Show Advanced Tags test updated tag, test updated tag 2 Priority LowMediumHigh Storage NoneHard DriveExternal HDNASBlu-rayDVD / CDRetail MangaMagazine How many volumes? Total TimesRe-read Re-read Value Select reread valueVery LowLowMediumHighVery High Comments test updated comment, test updated comment2 Ask to Discuss? Ask to discuss a chapter after you update the chapter #Don't ask to discuss Post to SNS Follow default settingPost with confirmationPost every time (without confirmation)Do not post \ 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; + } +}