• R/O
  • HTTP
  • SSH
  • HTTPS

コミット

タグ
未設定

よく使われているワード(クリックで追加)

javac++androidlinuxc#windowsobjective-ccocoa誰得qtpythonphprubygameguibathyscaphec計画中(planning stage)翻訳omegatframeworktwitterdomtestvb.netdirectxゲームエンジンbtronarduinopreviewer

なろうブックマーク分析用ツールのPrism+WPFサンプル実装


コミットメタ情報

リビジョン7c5d16549d2adb3f7aa393e55e6fb0af28411865 (tree)
日時2022-08-06 13:41:38
作者yoshy <yoshy.org.bitbucket@gz.j...>
コミッターyoshy

ログメッセージ

[MOD] なろうサービスから通信系の処理をリポジトリに分離

変更サマリ

差分

--- a/10Domain/Boundary/Repository/IAppConfigRepository.cs
+++ b/10Domain/Boundary/Repository/IAppConfigRepository.cs
@@ -1,4 +1,4 @@
1-using TestNarou.Domain.Model.App;
1+using TestNarou.Domain.Model.Config;
22
33 namespace TestNarou.Domain.Boundary.Repository
44 {
--- a/10Domain/Boundary/Service/IAppConfigService.cs
+++ b/10Domain/Boundary/Service/IAppConfigService.cs
@@ -1,4 +1,4 @@
1-using TestNarou.Domain.Model.App;
1+using TestNarou.Domain.Model.Config;
22
33 namespace TestNarou.Domain.Boundary.Service
44 {
--- a/10Domain/Model/App/AppConfig.cs
+++ b/10Domain/Model/Config/AppConfig.cs
@@ -1,4 +1,4 @@
1-namespace TestNarou.Domain.Model.App
1+namespace TestNarou.Domain.Model.Config
22 {
33 public record class AppConfig(
44 NarouLoginSettings NarouLoginSettings
--- a/10Domain/Model/App/NarouLoginSettings.cs
+++ b/10Domain/Model/Config/NarouLoginSettings.cs
@@ -1,6 +1,6 @@
11 using System;
22
3-namespace TestNarou.Domain.Model.App
3+namespace TestNarou.Domain.Model.Config
44 {
55 public record class NarouLoginSettings(
66 string NarouID,
--- a/10Domain/Model/Entity/Child/BookmarkCategoryRow.cs
+++ b/10Domain/Model/Entity/Child/BookmarkCategoryRow.cs
@@ -15,8 +15,8 @@ namespace TestNarou.Domain.Model.Entity.Child
1515 private readonly CompositeDisposable disposables = new();
1616 private bool disposedValue;
1717
18- public BookmarkCategoryRow(): this(
19- Index: -1,
18+ public BookmarkCategoryRow(int index): this(
19+ Index: index,
2020 Name: new(),
2121 Count: new(),
2222 IsSelected: new()
--- a/10Domain/Service/AppConfigService.cs
+++ b/10Domain/Service/AppConfigService.cs
@@ -1,6 +1,6 @@
11 using TestNarou.Domain.Boundary.Repository;
22 using TestNarou.Domain.Boundary.Service;
3-using TestNarou.Domain.Model.App;
3+using TestNarou.Domain.Model.Config;
44
55 namespace TestNarou.Domain.Service
66 {
--- a/10Domain/Service/NarouService.cs
+++ b/10Domain/Service/NarouService.cs
@@ -1,15 +1,13 @@
1-using CleanAuLait.Core.Converter;
2-using CleanAuLait.Domain.Service;
3-using NLog;
1+using NLog;
42 using Prism.Navigation;
53 using System.Collections.Generic;
64 using System.Linq;
7-using System.Text.RegularExpressions;
85 using TestNarou.Domain.Boundary.Service;
96 using TestNarou.Domain.Model.API;
107 using TestNarou.Domain.Model.Entity;
118 using TestNarou.Domain.Model.Entity.Child;
129 using TestNarou.Domain.Translator;
10+using TestNarou.OuterEdge.Boundary.Repository.API;
1311
1412 namespace TestNarou.Domain.Service
1513 {
@@ -17,55 +15,32 @@ namespace TestNarou.Domain.Service
1715 {
1816 private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
1917
20- private const string LOGIN_POST_URL = "https://ssl.syosetu.com/login/login/";
21- private const string LOGIN_REFFERER_URL = "https://ssl.syosetu.com/login/input/";
22-
23- private const string LOGOUT_POST_URL = "https://ssl.syosetu.com/login/logout/";
24- private const string LOGOUT_REFFERER_URL = "https://syosetu.com/user/top/";
25-
26- private const string BOOKMARK_CATEGORY_GET_URL = "https://syosetu.com/favnovelmain/list/";
27- private const string BOOKMARK_CATEGORY_REFFERER_URL = "https://syosetu.com/user/top/";
28-
29- private const string BOOKMARK_LIST_GET_URL = "https://syosetu.com/favnovelmain/list/?nowcategory={0}&order=new&p={1}";
30- private const string BOOKMARK_LIST_REFFERER_URL = "https://syosetu.com/favnovelmain/list/";
31-
32- private const string NOVEL_API_URL = "https://api.syosetu.com/novelapi/api/?out=json&gzip=5&ncode={0}&lim={1}";
33-
34- private const string LOGIN_FORM_KEY_ID = "narouid";
35- private const string LOGIN_FORM_KEY_PW = "pass";
36-
37- private const string LOGOUT_FORM_KEY_TOKEN = "token";
38-
39- private const string LOGIN_ERROR_MESSAGE = "エラーが発生しました";
40- private const string LOGIN_ERROR_MESSAGE_REQUIRED_FIELD = "IDまたはメールアドレスが未入力です。";
41- private const string LOGIN_ERROR_MESSAGE_INVALID_CREDENTIAL = "IDが存在しないかIDとパスワードが一致しません。";
42-
43- private const string DATA_TOKEN_REGEX = @"<a id=""logout"" class=""js-logout"" href=""https://syosetu.com/login/logout/"" data-token=""([0-9a-f]+)"">";
44-
45- private const string CATEGORY_LIST_REGEX = @"<ul class=""category_box"">(.+?)</ul>\s+<div class=""fav_box"">";
46- private const string CATEGORY_ITEM_REGEX = @"<li.*><a href=""/favnovelmain/list/\?nowcategory=\d+&order=[a-z]+"">(.+?)</a></li>";
47- private const string CATEGORY_NAME_COUNT_REGEX = @"(.+?)\((\d+)\)";
48-
49- private const string BOOKMARK_ENTRY_REGEX = @"<table class=""favnovel"">(.+?)</table>";
50- private const string BOOKMARK_ITEM_REGEX = @"<a class=""title"" href=""https://ncode.syosetu.com/(n[0-9a-z]+)/"">";
51-
5218 private string DataToken { get; set; }
5319
5420 private BookmarkCategory Categeory { get; set; }
5521 private BookmarkDetailList DetailList { get; set; }
5622
23+ private readonly INarouRepository repo;
24+
5725 private readonly IBookmarkCategoryRowTranslator vmCategoryRowTranslator;
5826 private readonly IBookmarkDetailListRowTranslator vmDetailRowTranslator;
5927
6028 public NarouService(
29+ INarouRepository repo,
6130 IBookmarkCategoryRowTranslator vmCategoryRowTranslator,
6231 IBookmarkDetailListRowTranslator vmDetailRowTranslator
6332 )
6433 {
34+ this.repo = repo;
35+
6536 this.vmCategoryRowTranslator = vmCategoryRowTranslator;
6637 this.vmDetailRowTranslator = vmDetailRowTranslator;
6738 }
6839
40+ public void Login(string id, string password)
41+ {
42+ this.DataToken = this.repo.Login(id, password);
43+ }
6944
7045 public void Logout()
7146 {
@@ -74,175 +49,31 @@ namespace TestNarou.Domain.Service
7449 return;
7550 }
7651
77- var form = new Dictionary<string, string>()
78- {
79- { LOGOUT_FORM_KEY_TOKEN, this.DataToken },
80- };
81-
82- var response = HttpClientHelper.PostAsync(LOGOUT_POST_URL, LOGOUT_REFFERER_URL, form).Result;
83-
84-#if false
85- if (!response.IsSuccessStatusCode)
86- {
87- throw new ServiceException($"ログアウト応答が想定と異なります。{response.StatusCode}");
88- }
89-#endif
90- }
91-
92- public void Login(string id, string password)
93- {
94- var form = new Dictionary<string, string>()
95- {
96- { LOGIN_FORM_KEY_ID, id },
97- { LOGIN_FORM_KEY_PW, password },
98- };
99-
100- var response = HttpClientHelper.PostAsync(LOGIN_POST_URL, LOGIN_REFFERER_URL, form).Result;
101-
102- if (!response.IsSuccessStatusCode)
103- {
104- throw new ServiceException($"ログイン応答が想定と異なります。{response.StatusCode}");
105- }
106-
107- string content = HttpClientHelper.ReadContentAsync(response).Result;
108-
109- if (content.Contains(LOGIN_ERROR_MESSAGE))
110- {
111- if (content.Contains(LOGIN_ERROR_MESSAGE_REQUIRED_FIELD))
112- {
113- throw new ServiceException(LOGIN_ERROR_MESSAGE_REQUIRED_FIELD);
114- }
115-
116- if (content.Contains(LOGIN_ERROR_MESSAGE_INVALID_CREDENTIAL))
117- {
118- throw new ServiceException(LOGIN_ERROR_MESSAGE_INVALID_CREDENTIAL);
119- }
120-
121- logger.Trace(content);
122-
123- throw new ServiceException("ログインエラーが発生しました。");
124- }
125-
126- //<a id="logout" class="js-logout" href="https://syosetu.com/login/logout/" data-token="27098ab9dd0d56d017c7cd3ac650cdeb">
127-
128- Regex regex = new(DATA_TOKEN_REGEX);
129- Match matcher = regex.Match(content);
130-
131- if (matcher.Success)
132- {
133- this.DataToken = matcher.Groups[1].Value;
134- }
135-
136- logger.Trace("DataToken {0}", this.DataToken);
52+ this.repo.Logout(this.DataToken);
13753 }
13854
13955 public BookmarkCategory GetBookmarkCategory()
14056 {
141- var response = HttpClientHelper.GetAsync(
142- BOOKMARK_CATEGORY_GET_URL, BOOKMARK_CATEGORY_REFFERER_URL).Result;
143-
144- if (!response.IsSuccessStatusCode)
145- {
146- throw new ServiceException($"応答が想定と異なります。{response.StatusCode}");
147- }
57+ IEnumerable<NarouCategoryInfo> infos = this.repo.GetNarouCategoryList();
14858
149- string content = HttpClientHelper.ReadContentAsync(response).Result;
150-
151- Regex regex = new(CATEGORY_LIST_REGEX, RegexOptions.Singleline);
152- Regex regexItem = new(CATEGORY_ITEM_REGEX, RegexOptions.Multiline);
153- Regex regexNameCount = new(CATEGORY_NAME_COUNT_REGEX);
154-
155- Match matcher = regex.Match(content);
156-
157- IList<NarouCategoryInfo> rows = new List<NarouCategoryInfo>();
158-
159- if (matcher.Success)
160- {
161- string categoryList = matcher.Groups[1].Value;
162-
163- //logger.Trace("categoryList: {0}", categoryList);
164-
165- MatchCollection matcherItems = regexItem.Matches(categoryList);
166-
167- foreach (Match matchItem in matcherItems)
168- {
169- string item = matchItem.Groups[1].Value;
170-
171- logger.Trace("categoryItem: {0}", item);
172-
173- Match matcherNameCount = regexNameCount.Match(item);
174-
175- if (matcherNameCount.Success)
176- {
177- string title = matcherNameCount.Groups[1].Value;
178- int count = int.Parse(matcherNameCount.Groups[2].Value);
179- //logger.Trace("categoryNameCount: {0} {1}", title, count);
180-
181- rows.Add(new(title, count));
182- }
183- }
184- }
185-
186- IList<BookmarkCategoryRow> rows2 = new List<BookmarkCategoryRow>();
187-
188- // TODO LINQ ZIP
189-
190- int rowIndex = 1;
191-
192- foreach (var row in rows.Select(v => this.vmCategoryRowTranslator.Translate(v)))
193- {
194- rows2.Add(row with { Index = rowIndex++ });
195- }
59+ IEnumerable<BookmarkCategoryRow> rows = infos.Zip(
60+ Enumerable.Range(1, infos.Count()),
61+ (category, no) => (category, no)
62+ ).Select(
63+ pair => this.vmCategoryRowTranslator.Translate(pair.category, pair.no)
64+ );
19665
19766 this.Categeory?.Dispose();
198- this.Categeory = new(rows2);
67+ this.Categeory = new(rows);
19968
20069 return this.Categeory;
20170 }
20271
20372 public BookmarkDetailList GetBookmarkDetailList(int categoryNo)
20473 {
205- IList<string> ncodeList = new List<string>();
206-
207- int count = this.Categeory.Rows[categoryNo - 1].Count.Value;
208-
209- for (int page = 1; page <= (count - 1) / 50 + 1; page++)
210- {
211- var response = HttpClientHelper.GetAsync(
212- string.Format(BOOKMARK_LIST_GET_URL, categoryNo, page), BOOKMARK_LIST_REFFERER_URL).Result;
213-
214- if (!response.IsSuccessStatusCode)
215- {
216- throw new ServiceException($"応答が想定と異なります。{response.StatusCode}");
217- }
218-
219- string content = HttpClientHelper.ReadContentAsync(response).Result;
220-
221- Regex regex = new(BOOKMARK_ENTRY_REGEX, RegexOptions.Singleline);
222- Regex regexItem = new(BOOKMARK_ITEM_REGEX, RegexOptions.Singleline);
223-
224- MatchCollection matchers = regex.Matches(content);
225-
226- foreach (Match matcher in matchers)
227- {
228- string entry = matcher.Groups[1].Value;
229-
230- Match matcherItem = regexItem.Match(entry);
231-
232- if (matcherItem.Success)
233- {
234- string ncode = matcherItem.Groups[1].Value;
235-
236- logger.Trace("bookmark: {0}", ncode);
237-
238- ncodeList.Add(ncode);
239- }
240- }
241- }
242-
243- string narouJson = GetNarouJson(ncodeList);
244-
245- NarouNovelDetailInfo[] novelInfos = narouJson.DeserializeJson<NarouNovelDetailInfo[]>();
74+ IEnumerable<NarouNovelDetailInfo> novelInfos =
75+ this.repo.GetNovelDetailInfoList(
76+ categoryNo, this.Categeory.Rows[categoryNo - 1].Count.Value);
24677
24778 IEnumerable<BookmarkDetailListRow> detailRows =
24879 novelInfos.Skip(1).Select(v => vmDetailRowTranslator.Translate(v));
@@ -253,26 +84,12 @@ namespace TestNarou.Domain.Service
25384 return this.DetailList;
25485 }
25586
256- private static string GetNarouJson(IList<string> ncodeList)
257- {
258- string apiUrl = string.Format(NOVEL_API_URL, string.Join('-', ncodeList), ncodeList.Count);
259-
260- var response = HttpClientHelper.GetAsync(apiUrl, null).Result;
261-
262- if (!response.IsSuccessStatusCode)
263- {
264- throw new ServiceException($"応答が想定と異なります。{response.StatusCode}");
265- }
266-
267- //string json = HttpClientHelper.ReadContentAsync(response).Result;
268- string json = HttpClientHelper.ReadGzipContent(response);
269-
270- return json;
271- }
272-
27387 public void Destroy()
27488 {
275- HttpClientHelper.Destroy();
89+ this.Categeory?.Dispose();
90+ this.DetailList?.Dispose();
91+
92+ this.repo?.Destroy();
27693 }
27794 }
27895 }
--- a/10Domain/Translator/BookmarkCategoryRowTranslator.cs
+++ b/10Domain/Translator/BookmarkCategoryRowTranslator.cs
@@ -17,10 +17,10 @@ namespace TestNarou.Domain.Translator
1717 this.mapper = mapper;
1818 }
1919
20- public BookmarkCategoryRow Translate(NarouCategoryInfo info)
20+ public BookmarkCategoryRow Translate(NarouCategoryInfo info, int index)
2121 {
2222 BookmarkCategoryRow row = info != null
23- ? mapper.Map(info, new BookmarkCategoryRow())
23+ ? mapper.Map(info, new BookmarkCategoryRow(index))
2424 : null;
2525
2626 return row;
--- a/10Domain/Translator/IBookmarkCategoryRowTranslator.cs
+++ b/10Domain/Translator/IBookmarkCategoryRowTranslator.cs
@@ -5,6 +5,6 @@ namespace TestNarou.Domain.Translator
55 {
66 internal interface IBookmarkCategoryRowTranslator
77 {
8- BookmarkCategoryRow Translate(NarouCategoryInfo info);
8+ BookmarkCategoryRow Translate(NarouCategoryInfo info, int index);
99 }
1010 }
\ No newline at end of file
--- a/20UseCase/Interactor/AppConfigLoadInteractor.cs
+++ b/20UseCase/Interactor/AppConfigLoadInteractor.cs
@@ -2,7 +2,7 @@
22 using CleanAuLait.UseCase.Interactor;
33 using CleanAuLait.UseCase.Response;
44 using TestNarou.Domain.Boundary.Service;
5-using TestNarou.Domain.Model.App;
5+using TestNarou.Domain.Model.Config;
66 using TestNarou.Domain.Model.Entity;
77 using TestNarou.UseCase.Boundary.Interactor;
88 using TestNarou.UseCase.Boundary.Presenter;
--- a/20UseCase/Interactor/AppConfigSaveInteractor.cs
+++ b/20UseCase/Interactor/AppConfigSaveInteractor.cs
@@ -2,10 +2,7 @@
22 using CleanAuLait.UseCase.Interactor;
33 using CleanAuLait.UseCase.Response;
44 using TestNarou.Domain.Boundary.Service;
5-using TestNarou.Domain.Model.App;
6-using TestNarou.Domain.Model.Entity;
75 using TestNarou.UseCase.Boundary.Interactor;
8-using TestNarou.UseCase.Boundary.Presenter;
96 using TestNarou.UseCase.Request;
107 using TestNarou.UseCase.Response;
118
--- a/20UseCase/Response/AppConfigLoadResponse.cs
+++ b/20UseCase/Response/AppConfigLoadResponse.cs
@@ -1,5 +1,5 @@
11 using CleanAuLait.UseCase.Response;
2-using TestNarou.Domain.Model.App;
2+using TestNarou.Domain.Model.Config;
33
44 namespace TestNarou.UseCase.Response
55 {
--- a/20UseCase/Response/AppConfigSaveResponse.cs
+++ b/20UseCase/Response/AppConfigSaveResponse.cs
@@ -1,5 +1,5 @@
11 using CleanAuLait.UseCase.Response;
2-using TestNarou.Domain.Model.App;
2+using TestNarou.Domain.Model.Config;
33
44 namespace TestNarou.UseCase.Response
55 {
--- a/30Adaptor/Boundary/Gateway/ViewModel/ILoginFormViewModel.cs
+++ b/30Adaptor/Boundary/Gateway/ViewModel/ILoginFormViewModel.cs
@@ -1,6 +1,6 @@
11 using Reactive.Bindings;
22 using System;
3-using TestNarou.Domain.Model.App;
3+using TestNarou.Domain.Model.Config;
44
55 namespace TestNarou.Adaptor.Boundary.Gateway.ViewModel
66 {
--- a/30Adaptor/Gateway/ViewModel/LoginFormViewModel.cs
+++ b/30Adaptor/Gateway/ViewModel/LoginFormViewModel.cs
@@ -7,7 +7,7 @@ using System.ComponentModel;
77 using System.Reactive.Disposables;
88 using TestNarou.Adaptor.Boundary.Controller;
99 using TestNarou.Adaptor.Boundary.Gateway.ViewModel;
10-using TestNarou.Domain.Model.App;
10+using TestNarou.Domain.Model.Config;
1111 using TestNarou.UseCase.Request;
1212
1313 namespace TestNarou.Adaptor.Gateway.ViewModel
--- /dev/null
+++ b/40OuterEdge/Boundary/Repository/INarouRepository.cs
@@ -0,0 +1,15 @@
1+using Prism.Navigation;
2+using System.Collections.Generic;
3+using TestNarou.Domain.Model.API;
4+
5+namespace TestNarou.OuterEdge.Boundary.Repository.API
6+{
7+ internal interface INarouRepository : IDestructible
8+ {
9+ string Login(string id, string password);
10+ void Logout(string dataToken);
11+
12+ IEnumerable<NarouCategoryInfo> GetNarouCategoryList();
13+ IEnumerable<NarouNovelDetailInfo> GetNovelDetailInfoList(int categoryNo, int categoryItemsCount);
14+ }
15+}
\ No newline at end of file
--- a/40OuterEdge/OuterEdgeModule.cs
+++ b/40OuterEdge/OuterEdgeModule.cs
@@ -5,6 +5,8 @@ using Prism.Mvvm;
55 using Prism.Regions;
66 using TestNarou.Adaptor.Boundary.Gateway.ViewModel;
77 using TestNarou.Domain.Boundary.Repository;
8+using TestNarou.OuterEdge.Boundary.Repository.API;
9+using TestNarou.OuterEdge.Repository.API;
810 using TestNarou.OuterEdge.Repository.Setting;
911 using TestNarou.OuterEdge.UI.View;
1012
@@ -28,6 +30,7 @@ namespace TestNarou.OuterEdge
2830 ///
2931
3032 containerRegistry.RegisterSingleton<IAppConfigRepository, AppConfigRepository>();
33+ containerRegistry.RegisterSingleton<INarouRepository, NarouRepository>();
3134
3235 logger.Trace("RegisterTypes end");
3336 }
--- a/10Domain/Service/HttpClientHelper.cs
+++ b/40OuterEdge/Repository/API/HttpClientHelper.cs
@@ -1,17 +1,17 @@
11 using NLog;
2+using Prism.Navigation;
23 using System;
34 using System.Collections.Generic;
45 using System.IO;
56 using System.IO.Compression;
6-using System.Linq;
77 using System.Net;
88 using System.Net.Http;
99 using System.Text;
1010 using System.Threading.Tasks;
1111
12-namespace TestNarou.Domain.Service
12+namespace TestNarou.OuterEdge.Repository.API
1313 {
14- internal static class HttpClientHelper
14+ internal class HttpClientHelper : IDestructible
1515 {
1616 private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
1717
@@ -20,13 +20,13 @@ namespace TestNarou.Domain.Service
2020 private const string REQUEST_HEADER_VALUE_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36";
2121 private const string REQUEST_HEADER_KEY_REFERRER = "Referer";
2222
23- private const string RESPONSE_HEADER_KEY_COOKIE = "Set-Cookie";
23+ //private const string RESPONSE_HEADER_KEY_COOKIE = "Set-Cookie";
2424
25- private static readonly CookieContainer cookies;
26- private static readonly HttpClientHandler httpClientHandler;
27- private static readonly HttpClient httpClient;
25+ private readonly CookieContainer cookies;
26+ private readonly HttpClientHandler httpClientHandler;
27+ private readonly HttpClient httpClient;
2828
29- static HttpClientHelper()
29+ public HttpClientHelper()
3030 {
3131 cookies = new();
3232
@@ -39,7 +39,7 @@ namespace TestNarou.Domain.Service
3939 httpClient = new(httpClientHandler);
4040
4141 // dummy cookies
42- Uri uri = new Uri("https://syosetu.com");
42+ Uri uri = new("https://syosetu.com");
4343
4444 (string key, string value)[] keyValues = new[]
4545 {
@@ -59,14 +59,14 @@ namespace TestNarou.Domain.Service
5959 ("_ga", "GA1.2.636765982.1649058346"),
6060 };
6161
62- foreach (var keyValue in keyValues)
62+ foreach (var (key, value) in keyValues)
6363 {
64- Cookie cookie = new Cookie(keyValue.key, keyValue.value);
64+ Cookie cookie = new(key, value);
6565 cookies.Add(uri, cookie);
6666 }
6767 }
6868
69- public static async Task<HttpResponseMessage> GetAsync(string url, string referer)
69+ public async Task<HttpResponseMessage> GetAsync(string url, string referer)
7070 {
7171 var request = CreateRequestMessage(url, referer, HttpMethod.Post);
7272
@@ -75,21 +75,21 @@ namespace TestNarou.Domain.Service
7575 var response = await httpClient.SendAsync(request).ConfigureAwait(false);
7676
7777 ShowResponseHeaders(response);
78- ShowCookies(response);
78+ ShowCookies();
7979
8080 logger.Trace("StatusCode: {0}", response.StatusCode);
8181
8282 return response;
8383 }
8484
85- public static async Task<HttpResponseMessage> PostAsync(string url, string referer, IDictionary<string, string> form)
85+ public async Task<HttpResponseMessage> PostAsync(string url, string referer, IDictionary<string, string> form)
8686 {
8787 var content = new FormUrlEncodedContent(form);
8888
8989 return await PostAsync(url, referer, content).ConfigureAwait(false);
9090 }
9191
92- public static async Task<HttpResponseMessage> PostAsync(string url, string referer, FormUrlEncodedContent content)
92+ public async Task<HttpResponseMessage> PostAsync(string url, string referer, FormUrlEncodedContent content)
9393 {
9494 var request = CreateRequestMessage(url, referer, HttpMethod.Post);
9595
@@ -100,14 +100,14 @@ namespace TestNarou.Domain.Service
100100 var response = await httpClient.SendAsync(request).ConfigureAwait(false);
101101
102102 ShowResponseHeaders(response);
103- ShowCookies(response);
103+ ShowCookies();
104104
105105 logger.Trace("StatusCode: {0}", response.StatusCode);
106106
107107 return response;
108108 }
109109
110- public static HttpRequestMessage CreateRequestMessage(string url, string referer, HttpMethod method)
110+ private static HttpRequestMessage CreateRequestMessage(string url, string referer, HttpMethod method)
111111 {
112112 var request = new HttpRequestMessage(method, url);
113113
@@ -139,13 +139,8 @@ namespace TestNarou.Domain.Service
139139 return request;
140140 }
141141
142- private static void ShowCookies(HttpResponseMessage response)
142+ private void ShowCookies()
143143 {
144- //foreach (var cookie in response.Headers.GetValues(RESPONSE_HEADER_KEY_COOKIE))
145- //{
146- // logger.Trace("Set-Cookie: {0}", cookie);
147- //}
148-
149144 foreach (Cookie cookie in cookies.GetAllCookies())
150145 {
151146 logger.Trace("Cookie: {0}, {1}, {2}, {3}",
@@ -181,7 +176,7 @@ namespace TestNarou.Domain.Service
181176 return Encoding.UTF8.GetString(raw);
182177 }
183178
184- public static void Destroy()
179+ public void Destroy()
185180 {
186181 httpClient.Dispose();
187182 }
--- /dev/null
+++ b/40OuterEdge/Repository/API/NarouRepository.cs
@@ -0,0 +1,260 @@
1+using CleanAuLait.Core.Converter;
2+using CleanAuLait.OuterEdge.Repository;
3+using NLog;
4+using System.Collections.Generic;
5+using System.Text.RegularExpressions;
6+using TestNarou.Domain.Model.API;
7+using TestNarou.OuterEdge.Boundary.Repository.API;
8+
9+namespace TestNarou.OuterEdge.Repository.API
10+{
11+ internal class NarouRepository : INarouRepository
12+ {
13+ private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
14+
15+ private const string LOGIN_POST_URL = "https://ssl.syosetu.com/login/login/";
16+ private const string LOGIN_REFFERER_URL = "https://ssl.syosetu.com/login/input/";
17+
18+ private const string LOGOUT_POST_URL = "https://ssl.syosetu.com/login/logout/";
19+ private const string LOGOUT_REFFERER_URL = "https://syosetu.com/user/top/";
20+
21+ private const string BOOKMARK_CATEGORY_GET_URL = "https://syosetu.com/favnovelmain/list/";
22+ private const string BOOKMARK_CATEGORY_REFFERER_URL = "https://syosetu.com/user/top/";
23+
24+ private const string BOOKMARK_LIST_GET_URL = "https://syosetu.com/favnovelmain/list/?nowcategory={0}&order=new&p={1}";
25+ private const string BOOKMARK_LIST_REFFERER_URL = "https://syosetu.com/favnovelmain/list/";
26+
27+ private const string NOVEL_API_URL = "https://api.syosetu.com/novelapi/api/?out=json&gzip=5&ncode={0}&lim={1}";
28+
29+ private const string LOGIN_FORM_KEY_ID = "narouid";
30+ private const string LOGIN_FORM_KEY_PW = "pass";
31+
32+ private const string LOGOUT_FORM_KEY_TOKEN = "token";
33+
34+ private const string LOGIN_ERROR_MESSAGE = "エラーが発生しました";
35+ private const string LOGIN_ERROR_MESSAGE_REQUIRED_FIELD = "IDまたはメールアドレスが未入力です。";
36+ private const string LOGIN_ERROR_MESSAGE_INVALID_CREDENTIAL = "IDが存在しないかIDとパスワードが一致しません。";
37+
38+ private const string DATA_TOKEN_REGEX = @"<a id=""logout"" class=""js-logout"" href=""https://syosetu.com/login/logout/"" data-token=""([0-9a-f]+)"">";
39+
40+ private const string CATEGORY_LIST_REGEX = @"<ul class=""category_box"">(.+?)</ul>\s+<div class=""fav_box"">";
41+ private const string CATEGORY_ITEM_REGEX = @"<li.*><a href=""/favnovelmain/list/\?nowcategory=\d+&order=[a-z]+"">(.+?)</a></li>";
42+ private const string CATEGORY_NAME_COUNT_REGEX = @"(.+?)\((\d+)\)";
43+
44+ private const string BOOKMARK_ENTRY_REGEX = @"<table class=""favnovel"">(.+?)</table>";
45+ private const string BOOKMARK_ITEM_REGEX = @"<a class=""title"" href=""https://ncode.syosetu.com/(n[0-9a-z]+)/"">";
46+
47+ private readonly HttpClientHelper httpClient = new();
48+
49+ public string Login(string id, string password)
50+ {
51+ var form = new Dictionary<string, string>()
52+ {
53+ { LOGIN_FORM_KEY_ID, id },
54+ { LOGIN_FORM_KEY_PW, password },
55+ };
56+
57+ var response = this.httpClient.PostAsync(LOGIN_POST_URL, LOGIN_REFFERER_URL, form).Result;
58+
59+ if (!response.IsSuccessStatusCode)
60+ {
61+ throw new RepositoryException($"ログイン応答が想定と異なります。{response.StatusCode}");
62+ }
63+
64+ string content = HttpClientHelper.ReadContentAsync(response).Result;
65+
66+ if (content.Contains(LOGIN_ERROR_MESSAGE))
67+ {
68+ if (content.Contains(LOGIN_ERROR_MESSAGE_REQUIRED_FIELD))
69+ {
70+ throw new RepositoryException(LOGIN_ERROR_MESSAGE_REQUIRED_FIELD);
71+ }
72+
73+ if (content.Contains(LOGIN_ERROR_MESSAGE_INVALID_CREDENTIAL))
74+ {
75+ throw new RepositoryException(LOGIN_ERROR_MESSAGE_INVALID_CREDENTIAL);
76+ }
77+
78+ logger.Trace(content);
79+
80+ throw new RepositoryException("ログインエラーが発生しました。");
81+ }
82+
83+ //<a id="logout" class="js-logout" href="https://syosetu.com/login/logout/" data-token="27098ab9dd0d56d017c7cd3ac650cdeb">
84+
85+ Regex regex = new(DATA_TOKEN_REGEX);
86+ Match matcher = regex.Match(content);
87+
88+ if (!matcher.Success)
89+ {
90+ throw new RepositoryException("ログイン後に DataToken が見つかりませんでした。");
91+ }
92+
93+ string dataToken = matcher.Groups[1].Value;
94+
95+ logger.Trace("DataToken {0}", dataToken);
96+
97+ return dataToken;
98+ }
99+
100+ public void Logout(string dataToken)
101+ {
102+ if (string.IsNullOrEmpty(dataToken))
103+ {
104+ return;
105+ }
106+
107+ var form = new Dictionary<string, string>()
108+ {
109+ { LOGOUT_FORM_KEY_TOKEN, dataToken },
110+ };
111+
112+ var response = this.httpClient.PostAsync(LOGOUT_POST_URL, LOGOUT_REFFERER_URL, form).Result;
113+
114+#if false
115+ if (!response.IsSuccessStatusCode)
116+ {
117+ throw new RepositoryException($"ログアウト応答が想定と異なります。{response.StatusCode}");
118+ }
119+#endif
120+ }
121+
122+ public IEnumerable<NarouCategoryInfo> GetNarouCategoryList()
123+ {
124+ var response = this.httpClient.GetAsync(
125+ BOOKMARK_CATEGORY_GET_URL, BOOKMARK_CATEGORY_REFFERER_URL).Result;
126+
127+ if (!response.IsSuccessStatusCode)
128+ {
129+ throw new RepositoryException($"応答が想定と異なります。{response.StatusCode}");
130+ }
131+
132+ string content = HttpClientHelper.ReadContentAsync(response).Result;
133+
134+ Regex regex = new(CATEGORY_LIST_REGEX, RegexOptions.Singleline);
135+ Regex regexItem = new(CATEGORY_ITEM_REGEX, RegexOptions.Multiline);
136+ Regex regexNameCount = new(CATEGORY_NAME_COUNT_REGEX);
137+
138+ Match matcher = regex.Match(content);
139+
140+ if (!matcher.Success)
141+ {
142+ throw new RepositoryException("ブックマークカテゴリが見つかりません。");
143+ }
144+
145+ string categoryList = matcher.Groups[1].Value;
146+
147+ //logger.Trace("categoryList: {0}", categoryList);
148+
149+ MatchCollection matcherItems = regexItem.Matches(categoryList);
150+
151+ if (matcherItems.Count == 0)
152+ {
153+ throw new RepositoryException("ブックマークカテゴリの明細情報が見つかりません。");
154+ }
155+
156+ IList<NarouCategoryInfo> infos = new List<NarouCategoryInfo>();
157+
158+ foreach (Match matchItem in matcherItems)
159+ {
160+ string item = matchItem.Groups[1].Value;
161+
162+ logger.Trace("categoryItem: {0}", item);
163+
164+ Match matcherNameCount = regexNameCount.Match(item);
165+
166+ if (!matcherNameCount.Success)
167+ {
168+ throw new RepositoryException("ブックマークカテゴリ明細情報の形式が想定と異なります。");
169+ }
170+
171+ string title = matcherNameCount.Groups[1].Value;
172+ int count = int.Parse(matcherNameCount.Groups[2].Value);
173+ //logger.Trace("categoryNameCount: {0} {1}", title, count);
174+
175+ infos.Add(new(title, count));
176+ }
177+
178+ return infos;
179+ }
180+
181+ public IEnumerable<NarouNovelDetailInfo> GetNovelDetailInfoList(int categoryNo, int categoryItemsCount)
182+ {
183+ IList<string> ncodeList = new List<string>();
184+
185+ for (int page = 1; page <= (categoryItemsCount - 1) / 50 + 1; page++)
186+ {
187+ var response = this.httpClient.GetAsync(
188+ string.Format(BOOKMARK_LIST_GET_URL, categoryNo, page), BOOKMARK_LIST_REFFERER_URL).Result;
189+
190+ if (!response.IsSuccessStatusCode)
191+ {
192+ throw new RepositoryException($"応答が想定と異なります。{response.StatusCode}");
193+ }
194+
195+ string content = HttpClientHelper.ReadContentAsync(response).Result;
196+
197+ Regex regex = new(BOOKMARK_ENTRY_REGEX, RegexOptions.Singleline);
198+ Regex regexItem = new(BOOKMARK_ITEM_REGEX, RegexOptions.Singleline);
199+
200+ MatchCollection matchers = regex.Matches(content);
201+
202+ if (matchers.Count == 0)
203+ {
204+ throw new RepositoryException("ブックマークの明細リストが見つかりません。");
205+ }
206+
207+ foreach (Match matcher in matchers)
208+ {
209+ string entry = matcher.Groups[1].Value;
210+
211+ Match matcherItem = regexItem.Match(entry);
212+
213+ if (!matcherItem.Success)
214+ {
215+ throw new RepositoryException("ブックマーク明細情報の形式が想定と異なります。");
216+ }
217+
218+ string ncode = matcherItem.Groups[1].Value;
219+
220+ logger.Trace("bookmark: {0}", ncode);
221+
222+ ncodeList.Add(ncode);
223+ }
224+ }
225+
226+ string narouJson = GetNarouJson(ncodeList);
227+
228+ NarouNovelDetailInfo[] novelInfos = narouJson.DeserializeJson<NarouNovelDetailInfo[]>();
229+
230+ if (novelInfos == null)
231+ {
232+ throw new RepositoryException("なろうAPIからの応答が想定と異なります");
233+ }
234+
235+ return novelInfos;
236+ }
237+
238+ private string GetNarouJson(IList<string> ncodeList)
239+ {
240+ string apiUrl = string.Format(NOVEL_API_URL, string.Join('-', ncodeList), ncodeList.Count);
241+
242+ var response = this.httpClient.GetAsync(apiUrl, null).Result;
243+
244+ if (!response.IsSuccessStatusCode)
245+ {
246+ throw new RepositoryException($"応答が想定と異なります。{response.StatusCode}");
247+ }
248+
249+ string json = HttpClientHelper.ReadGzipContent(response);
250+
251+ return json;
252+ }
253+
254+ public void Destroy()
255+ {
256+ this.httpClient?.Destroy();
257+ }
258+
259+ }
260+}
--- a/40OuterEdge/Repository/Config/AppConfigRepository.cs
+++ b/40OuterEdge/Repository/Config/AppConfigRepository.cs
@@ -1,19 +1,26 @@
11 using CleanAuLait.Core.Converter;
2+using NLog;
23 using TestNarou.Domain.Boundary.Repository;
3-using TestNarou.Domain.Model.App;
4+using TestNarou.Domain.Model.Config;
45 using TestNarou.Properties;
56
67 namespace TestNarou.OuterEdge.Repository.Setting
78 {
89 internal class AppConfigRepository : IAppConfigRepository
910 {
11+ private static readonly ILogger logger = LogManager.GetCurrentClassLogger();
12+
1013 public AppConfig Load()
1114 {
1215 string json = Settings.Default.AppConfigJson;
1316
14- AppConfig appConfig = !string.IsNullOrEmpty(json)
15- ? json.DeserializeJson<AppConfig>()
16- : new();
17+ AppConfig appConfig = json.DeserializeJson<AppConfig>();
18+
19+ if (appConfig == null)
20+ {
21+ logger.Warn("restore application settings failed.");
22+ appConfig = new();
23+ }
1724
1825 return appConfig;
1926 }
--- a/App.xaml.cs
+++ b/App.xaml.cs
@@ -4,7 +4,6 @@ using CleanAuLait.Core.Log;
44 using NLog;
55 using Prism.Ioc;
66 using Prism.Modularity;
7-using Prism.Regions;
87 using Prism.Unity;
98 using System;
109 using System.Reflection;
@@ -13,7 +12,6 @@ using System.Windows;
1312 using System.Windows.Threading;
1413 using TestNarou.Adaptor;
1514 using TestNarou.Adaptor.Boundary.Controller;
16-using TestNarou.Adaptor.Boundary.Gateway.ViewModel;
1715 using TestNarou.Core.Mapper;
1816 using TestNarou.Domain;
1917 using TestNarou.Infra;
@@ -207,17 +205,20 @@ namespace TestNarou
207205 logger.Info("{0} v{1}.{2}.{3}.{4}",
208206 asm.Name, ver.Major, ver.Minor, ver.Build, ver.Revision);
209207
210- {
211- var wc = Container.Resolve<IAppWindowController>(); ;
208+ PrepareInitialView();
212209
213- // Load Application Settings
214- _ = wc.Execute(new AppConfigLoadRequest());
210+ logger.Trace("OnStartup end");
211+ }
215212
216- // Navigate Login Form View
217- wc.NavigateLoginFormView();
218- }
213+ private void PrepareInitialView()
214+ {
215+ var wc = Container.Resolve<IAppWindowController>(); ;
219216
220- logger.Trace("OnStartup end");
217+ // Load Application Settings
218+ _ = wc.Execute(new AppConfigLoadRequest());
219+
220+ // Navigate Login Form View
221+ wc.NavigateLoginFormView();
221222 }
222223
223224 protected override void OnExit(ExitEventArgs e)