├── .gitattributes
├── .gitignore
├── EHentaiAPI.TestConsole
├── EHentaiAPI.TestConsole.csproj
├── FileSharedPreferences.cs
└── Program.cs
├── EHentaiAPI.UnitTest
├── EHentaiAPI.UnitTest.csproj
├── Gallery.cs
├── Login.cs
├── ShareClient.cs
└── TestSetting.cs
├── EHentaiAPI.sln
├── EHentaiAPI
├── Client
│ ├── Data
│ │ ├── FavListUrlBuilder.cs
│ │ ├── GalleryComment.cs
│ │ ├── GalleryCommentList.cs
│ │ ├── GalleryDetail.cs
│ │ ├── GalleryInfo.cs
│ │ ├── GalleryPreview.cs
│ │ ├── GalleryTagGroup.cs
│ │ ├── LargePreviewSet.cs
│ │ ├── ListUrlBuilder.cs
│ │ ├── NormalPreviewSet.cs
│ │ └── PreviewSet.cs
│ ├── EhCacheKeyFactory.cs
│ ├── EhClient.cs
│ ├── EhConfig.cs
│ ├── EhEngine.cs
│ ├── EhRequest.cs
│ ├── EhRequestBuilder.cs
│ ├── EhRequestCallback.cs
│ ├── EhTask.cs
│ ├── EhUrl.cs
│ ├── EhUtils.cs
│ ├── Exceptions
│ │ ├── EhException.cs
│ │ ├── NoHAtHClientException.cs
│ │ ├── OffensiveException.cs
│ │ ├── ParseException.cs
│ │ ├── PiningException.cs
│ │ └── StatusCodeException.cs
│ └── Parser
│ │ ├── ArchiveParser.cs
│ │ ├── EventPaneParser.cs
│ │ ├── FavoritesParser.cs
│ │ ├── ForumsParser.cs
│ │ ├── GalleryApiParser.cs
│ │ ├── GalleryDetailParser.cs
│ │ ├── GalleryDetailUrlParser.cs
│ │ ├── GalleryListParser.cs
│ │ ├── GalleryMultiPageViewerPTokenParser.cs
│ │ ├── GalleryNotAvailableParser.cs
│ │ ├── GalleryPageApiParser.cs
│ │ ├── GalleryPageParser.cs
│ │ ├── GalleryPageUrlParser.cs
│ │ ├── GalleryTokenApiParser.cs
│ │ ├── ParserUtils.cs
│ │ ├── ProfileParser.cs
│ │ ├── RateGalleryParser.cs
│ │ ├── SignInParser.cs
│ │ ├── TorrentParser.cs
│ │ ├── VoteCommentParser.cs
│ │ └── VoteTagParser.cs
├── EHentaiAPI.csproj
├── ExtendFunction
│ ├── ILog.cs
│ ├── IRequest.cs
│ ├── IResponse.cs
│ ├── Log.cs
│ └── Request.cs
├── Settings.cs
└── Utils
│ ├── Document.cs
│ ├── EhPageImageSpider.cs
│ ├── ExtensionMethods
│ ├── AngleHtmlExtensionMethod.cs
│ ├── DateTimeExtensionMethod.cs
│ ├── ExceptionExtensionMethod.cs
│ └── JsonExtensionMethod.cs
│ ├── FullPreviewSetCollection.cs
│ ├── ISharedPreferences.cs
│ ├── SharedPreferences.cs
│ └── UrlBuilder.cs
├── LICENSE
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | ###############################################################################
2 | # Set default behavior to automatically normalize line endings.
3 | ###############################################################################
4 | * text=auto
5 |
6 | ###############################################################################
7 | # Set default behavior for command prompt diff.
8 | #
9 | # This is need for earlier builds of msysgit that does not have it on by
10 | # default for csharp files.
11 | # Note: This is only used by command line
12 | ###############################################################################
13 | #*.cs diff=csharp
14 |
15 | ###############################################################################
16 | # Set the merge driver for project and solution files
17 | #
18 | # Merging from the command prompt will add diff markers to the files if there
19 | # are conflicts (Merging from VS is not affected by the settings below, in VS
20 | # the diff markers are never inserted). Diff markers may cause the following
21 | # file extensions to fail to load in VS. An alternative would be to treat
22 | # these files as binary and thus will always conflict and require user
23 | # intervention with every merge. To do so, just uncomment the entries below
24 | ###############################################################################
25 | #*.sln merge=binary
26 | #*.csproj merge=binary
27 | #*.vbproj merge=binary
28 | #*.vcxproj merge=binary
29 | #*.vcproj merge=binary
30 | #*.dbproj merge=binary
31 | #*.fsproj merge=binary
32 | #*.lsproj merge=binary
33 | #*.wixproj merge=binary
34 | #*.modelproj merge=binary
35 | #*.sqlproj merge=binary
36 | #*.wwaproj merge=binary
37 |
38 | ###############################################################################
39 | # behavior for image files
40 | #
41 | # image files are treated as binary by default.
42 | ###############################################################################
43 | #*.jpg binary
44 | #*.png binary
45 | #*.gif binary
46 |
47 | ###############################################################################
48 | # diff behavior for common document formats
49 | #
50 | # Convert binary document formats to text before diffing them. This feature
51 | # is only available from the command line. Turn it on by uncommenting the
52 | # entries below.
53 | ###############################################################################
54 | #*.doc diff=astextplain
55 | #*.DOC diff=astextplain
56 | #*.docx diff=astextplain
57 | #*.DOCX diff=astextplain
58 | #*.dot diff=astextplain
59 | #*.DOT diff=astextplain
60 | #*.pdf diff=astextplain
61 | #*.PDF diff=astextplain
62 | #*.rtf diff=astextplain
63 | #*.RTF diff=astextplain
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 | ##
4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
5 |
6 | # User-specific files
7 | *.rsuser
8 | *.suo
9 | *.user
10 | *.userosscache
11 | *.sln.docstates
12 |
13 | # User-specific files (MonoDevelop/Xamarin Studio)
14 | *.userprefs
15 |
16 | # Mono auto generated files
17 | mono_crash.*
18 |
19 | # Build results
20 | [Dd]ebug/
21 | [Dd]ebugPublic/
22 | [Rr]elease/
23 | [Rr]eleases/
24 | x64/
25 | x86/
26 | [Ww][Ii][Nn]32/
27 | [Aa][Rr][Mm]/
28 | [Aa][Rr][Mm]64/
29 | bld/
30 | [Bb]in/
31 | [Oo]bj/
32 | [Oo]ut/
33 | [Ll]og/
34 | [Ll]ogs/
35 |
36 | # Visual Studio 2015/2017 cache/options directory
37 | .vs/
38 | # Uncomment if you have tasks that create the project's static files in wwwroot
39 | #wwwroot/
40 |
41 | # Visual Studio 2017 auto generated files
42 | Generated\ Files/
43 |
44 | # MSTest test Results
45 | [Tt]est[Rr]esult*/
46 | [Bb]uild[Ll]og.*
47 |
48 | # NUnit
49 | *.VisualState.xml
50 | TestResult.xml
51 | nunit-*.xml
52 |
53 | # Build Results of an ATL Project
54 | [Dd]ebugPS/
55 | [Rr]eleasePS/
56 | dlldata.c
57 |
58 | # Benchmark Results
59 | BenchmarkDotNet.Artifacts/
60 |
61 | # .NET Core
62 | project.lock.json
63 | project.fragment.lock.json
64 | artifacts/
65 |
66 | # ASP.NET Scaffolding
67 | ScaffoldingReadMe.txt
68 |
69 | # StyleCop
70 | StyleCopReport.xml
71 |
72 | # Files built by Visual Studio
73 | *_i.c
74 | *_p.c
75 | *_h.h
76 | *.ilk
77 | *.meta
78 | *.obj
79 | *.iobj
80 | *.pch
81 | *.pdb
82 | *.ipdb
83 | *.pgc
84 | *.pgd
85 | *.rsp
86 | *.sbr
87 | *.tlb
88 | *.tli
89 | *.tlh
90 | *.tmp
91 | *.tmp_proj
92 | *_wpftmp.csproj
93 | *.log
94 | *.vspscc
95 | *.vssscc
96 | .builds
97 | *.pidb
98 | *.svclog
99 | *.scc
100 |
101 | # Chutzpah Test files
102 | _Chutzpah*
103 |
104 | # Visual C++ cache files
105 | ipch/
106 | *.aps
107 | *.ncb
108 | *.opendb
109 | *.opensdf
110 | *.sdf
111 | *.cachefile
112 | *.VC.db
113 | *.VC.VC.opendb
114 |
115 | # Visual Studio profiler
116 | *.psess
117 | *.vsp
118 | *.vspx
119 | *.sap
120 |
121 | # Visual Studio Trace Files
122 | *.e2e
123 |
124 | # TFS 2012 Local Workspace
125 | $tf/
126 |
127 | # Guidance Automation Toolkit
128 | *.gpState
129 |
130 | # ReSharper is a .NET coding add-in
131 | _ReSharper*/
132 | *.[Rr]e[Ss]harper
133 | *.DotSettings.user
134 |
135 | # TeamCity is a build add-in
136 | _TeamCity*
137 |
138 | # DotCover is a Code Coverage Tool
139 | *.dotCover
140 |
141 | # AxoCover is a Code Coverage Tool
142 | .axoCover/*
143 | !.axoCover/settings.json
144 |
145 | # Coverlet is a free, cross platform Code Coverage Tool
146 | coverage*.json
147 | coverage*.xml
148 | coverage*.info
149 |
150 | # Visual Studio code coverage results
151 | *.coverage
152 | *.coveragexml
153 |
154 | # NCrunch
155 | _NCrunch_*
156 | .*crunch*.local.xml
157 | nCrunchTemp_*
158 |
159 | # MightyMoose
160 | *.mm.*
161 | AutoTest.Net/
162 |
163 | # Web workbench (sass)
164 | .sass-cache/
165 |
166 | # Installshield output folder
167 | [Ee]xpress/
168 |
169 | # DocProject is a documentation generator add-in
170 | DocProject/buildhelp/
171 | DocProject/Help/*.HxT
172 | DocProject/Help/*.HxC
173 | DocProject/Help/*.hhc
174 | DocProject/Help/*.hhk
175 | DocProject/Help/*.hhp
176 | DocProject/Help/Html2
177 | DocProject/Help/html
178 |
179 | # Click-Once directory
180 | publish/
181 |
182 | # Publish Web Output
183 | *.[Pp]ublish.xml
184 | *.azurePubxml
185 | # Note: Comment the next line if you want to checkin your web deploy settings,
186 | # but database connection strings (with potential passwords) will be unencrypted
187 | *.pubxml
188 | *.publishproj
189 |
190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
191 | # checkin your Azure Web App publish settings, but sensitive information contained
192 | # in these scripts will be unencrypted
193 | PublishScripts/
194 |
195 | # NuGet Packages
196 | *.nupkg
197 | # NuGet Symbol Packages
198 | *.snupkg
199 | # The packages folder can be ignored because of Package Restore
200 | **/[Pp]ackages/*
201 | # except build/, which is used as an MSBuild target.
202 | !**/[Pp]ackages/build/
203 | # Uncomment if necessary however generally it will be regenerated when needed
204 | #!**/[Pp]ackages/repositories.config
205 | # NuGet v3's project.json files produces more ignorable files
206 | *.nuget.props
207 | *.nuget.targets
208 |
209 | # Microsoft Azure Build Output
210 | csx/
211 | *.build.csdef
212 |
213 | # Microsoft Azure Emulator
214 | ecf/
215 | rcf/
216 |
217 | # Windows Store app package directories and files
218 | AppPackages/
219 | BundleArtifacts/
220 | Package.StoreAssociation.xml
221 | _pkginfo.txt
222 | *.appx
223 | *.appxbundle
224 | *.appxupload
225 |
226 | # Visual Studio cache files
227 | # files ending in .cache can be ignored
228 | *.[Cc]ache
229 | # but keep track of directories ending in .cache
230 | !?*.[Cc]ache/
231 |
232 | # Others
233 | ClientBin/
234 | ~$*
235 | *~
236 | *.dbmdl
237 | *.dbproj.schemaview
238 | *.jfm
239 | *.pfx
240 | *.publishsettings
241 | orleans.codegen.cs
242 |
243 | # Including strong name files can present a security risk
244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424)
245 | #*.snk
246 |
247 | # Since there are multiple workflows, uncomment next line to ignore bower_components
248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
249 | #bower_components/
250 |
251 | # RIA/Silverlight projects
252 | Generated_Code/
253 |
254 | # Backup & report files from converting an old project file
255 | # to a newer Visual Studio version. Backup files are not needed,
256 | # because we have git ;-)
257 | _UpgradeReport_Files/
258 | Backup*/
259 | UpgradeLog*.XML
260 | UpgradeLog*.htm
261 | ServiceFabricBackup/
262 | *.rptproj.bak
263 |
264 | # SQL Server files
265 | *.mdf
266 | *.ldf
267 | *.ndf
268 |
269 | # Business Intelligence projects
270 | *.rdl.data
271 | *.bim.layout
272 | *.bim_*.settings
273 | *.rptproj.rsuser
274 | *- [Bb]ackup.rdl
275 | *- [Bb]ackup ([0-9]).rdl
276 | *- [Bb]ackup ([0-9][0-9]).rdl
277 |
278 | # Microsoft Fakes
279 | FakesAssemblies/
280 |
281 | # GhostDoc plugin setting file
282 | *.GhostDoc.xml
283 |
284 | # Node.js Tools for Visual Studio
285 | .ntvs_analysis.dat
286 | node_modules/
287 |
288 | # Visual Studio 6 build log
289 | *.plg
290 |
291 | # Visual Studio 6 workspace options file
292 | *.opt
293 |
294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
295 | *.vbw
296 |
297 | # Visual Studio LightSwitch build output
298 | **/*.HTMLClient/GeneratedArtifacts
299 | **/*.DesktopClient/GeneratedArtifacts
300 | **/*.DesktopClient/ModelManifest.xml
301 | **/*.Server/GeneratedArtifacts
302 | **/*.Server/ModelManifest.xml
303 | _Pvt_Extensions
304 |
305 | # Paket dependency manager
306 | .paket/paket.exe
307 | paket-files/
308 |
309 | # FAKE - F# Make
310 | .fake/
311 |
312 | # CodeRush personal settings
313 | .cr/personal
314 |
315 | # Python Tools for Visual Studio (PTVS)
316 | __pycache__/
317 | *.pyc
318 |
319 | # Cake - Uncomment if you are using it
320 | # tools/**
321 | # !tools/packages.config
322 |
323 | # Tabs Studio
324 | *.tss
325 |
326 | # Telerik's JustMock configuration file
327 | *.jmconfig
328 |
329 | # BizTalk build output
330 | *.btp.cs
331 | *.btm.cs
332 | *.odx.cs
333 | *.xsd.cs
334 |
335 | # OpenCover UI analysis results
336 | OpenCover/
337 |
338 | # Azure Stream Analytics local run output
339 | ASALocalRun/
340 |
341 | # MSBuild Binary and Structured Log
342 | *.binlog
343 |
344 | # NVidia Nsight GPU debugger configuration file
345 | *.nvuser
346 |
347 | # MFractors (Xamarin productivity tool) working folder
348 | .mfractor/
349 |
350 | # Local History for Visual Studio
351 | .localhistory/
352 |
353 | # BeatPulse healthcheck temp database
354 | healthchecksdb
355 |
356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017
357 | MigrationBackup/
358 |
359 | # Ionide (cross platform F# VS Code tools) working folder
360 | .ionide/
361 |
362 | # Fody - auto-generated XML schema
363 | FodyWeavers.xsd
364 |
365 | # My Test Files.
366 | TestSetting.S.cs
--------------------------------------------------------------------------------
/EHentaiAPI.TestConsole/EHentaiAPI.TestConsole.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Exe
5 | net5.0
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/EHentaiAPI.TestConsole/FileSharedPreferences.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.ExtendFunction;
2 | using EHentaiAPI.Utils;
3 | using Newtonsoft.Json;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.IO;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace EHentaiAPI.TestConsole
12 | {
13 | public class FileSharedPreferences : ISharedPreferences
14 | {
15 | private Dictionary store = new Dictionary();
16 | private const string FILE_PATH = "config.json";
17 |
18 | public FileSharedPreferences()
19 | {
20 | if (File.Exists(FILE_PATH))
21 | {
22 | //Log.LogImplement.I("FileSharedPreferences", $"Load config from {FILE_PATH}");
23 | store = JsonConvert.DeserializeObject>(File.ReadAllText(FILE_PATH));
24 | }
25 | }
26 |
27 | public T getValue(string key, T defValue = default)
28 | {
29 | var value = store.TryGetValue(key, out var d) ? (T)d : defValue;
30 | //Log.LogImplement.I("FileSharedPreferences", $"Get {key} ===> {value} {(defValue.Equals(value) ? "(DEFAULT)" : "")}");
31 | return value;
32 | }
33 |
34 | public ISharedPreferences setValue(string key, T value = default)
35 | {
36 | //Log.LogImplement.I("FileSharedPreferences", $"Set {key} = {value}");
37 | store[key] = value;
38 | File.WriteAllText(FILE_PATH, JsonConvert.SerializeObject(store, Formatting.Indented));
39 | return this;
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/EHentaiAPI.TestConsole/Program.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client;
2 | using EHentaiAPI.Client.Data;
3 | using EHentaiAPI.Client.Parser;
4 | using EHentaiAPI.ExtendFunction;
5 | using EHentaiAPI.UnitTest;
6 | using EHentaiAPI.Utils;
7 | using Newtonsoft.Json;
8 | using System;
9 | using System.Collections.Generic;
10 | using System.IO;
11 | using System.Linq;
12 | using System.Threading.Tasks;
13 | using static EHentaiAPI.Settings;
14 |
15 | namespace EHentaiAPI.TestConsole
16 | {
17 | class Program
18 | {
19 | static async Task Main(string[] args)
20 | {
21 | var client = new EhClient();
22 |
23 | client.Settings = new Settings()
24 | {
25 | SharedPreferences = new FileSharedPreferences()
26 | };
27 | client.Settings.GallerySite = GallerySites.SITE_E;
28 | client.Cookies.Add(new System.Net.Cookie("sl", "dm_1", "/", "e-hentai.org"));
29 |
30 | await client.SignInAsync(TestSettings.UserName, TestSettings.Password);
31 |
32 | var detail = await client.GetGalleryDetailAsync("https://e-hentai.org/g/2083629/7adc18a5eb/");
33 |
34 | /*
35 | var voteResult = await client.VoteComment(detail.apiUid, detail.apiKey, detail.gid, detail.token, detail.comments.comments[1].id, 1);
36 |
37 | var torrentList = await client.GetTorrentList(detail.torrentUrl, detail.gid, detail.token);
38 |
39 | var archiveList = await client.GetArchiveList(detail.archiveUrl, detail.gid, detail.token);
40 |
41 | var voteTagResult = await client.VoteTag(detail.apiUid, detail.apiKey, detail.gid, detail.token, detail.tags.First().getTagAt(0), 1);
42 |
43 | var rateResult = await client.RateGallery(detail, 3);
44 |
45 | //var newCommentList = await client.CommentNewGallery("https://e-hentai.org/g/2062874/03037d8698/","谢谢好兄弟:D");
46 |
47 | //var newCommentList = await client.ModifyCommentGallery("https://e-hentai.org/g/2062874/03037d8698/", "谢谢好兄弟:D 233", detail.comments.comments.FirstOrDefault(x => x.user.Equals(TestSettings.UserName, StringComparison.InvariantCultureIgnoreCase)).id.ToString());
48 |
49 | var galleryToken = await client.GetGalleryToken("https://e-hentai.org/s/35142216f7/2062874-16");
50 |
51 | var favUrlBuilder = new FavListUrlBuilder(client.EhUrl);
52 | favUrlBuilder.setFavCat(FavListUrlBuilder.FAV_CAT_ALL);
53 | favUrlBuilder.setIndex(1);
54 | var favReqUrl = favUrlBuilder.build();
55 | var getFavorites = await client.GetFavorites(favReqUrl);
56 |
57 | //await client.AddFavorites(detail.gid,detail.token,1,"Haha");
58 |
59 | favUrlBuilder = new FavListUrlBuilder(client.EhUrl);
60 | favUrlBuilder.setFavCat(5);
61 | getFavorites = await client.GetFavorites(favUrlBuilder);
62 | await client.ModifyFavorites(favUrlBuilder, getFavorites.galleryInfoList.Select(x => x.gid).ToArray(), 3);
63 |
64 | var profile = await client.GetProfile();
65 |
66 | var preview = detail.PreviewSet.GetGalleryPreview(detail.Gid, 1);
67 | var nextPreview = detail.PreviewSet.GetGalleryPreview(detail.Gid, 2);
68 | var page = await client.GetGalleryPageAsync(detail, preview);
69 | var nextPage = await client.GetGalleryPageApiAsync(detail.Gid, nextPreview.Position, nextPreview.PToken, page.showKey, preview.PToken);
70 |
71 | var collection = new FullPreviewSetCollection(client, detail);
72 | var d10 = await collection.GetAsync(10);
73 | var d11 = await collection.GetAsync(40);
74 | */
75 | var spider = new EhPageImageSpider(client, detail, async (downloadUrl, reporter) =>
76 | {
77 | await Task.Delay(2000);
78 | return null;
79 | });
80 | client.Settings.SpiderBackPreloadCount = 6;
81 | client.Settings.SpiderFrontPreloadCount = 6;
82 |
83 | var task = spider.RequestPage(2);
84 | await task.DownloadTask;
85 |
86 | /*
87 | task = spider.RequestPage(6);
88 | task = spider.RequestPage(7);
89 | task = spider.RequestPage(8);
90 | */
91 |
92 | await Task.Delay(10000);
93 |
94 | Console.ReadLine();
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/EHentaiAPI.UnitTest/EHentaiAPI.UnitTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 |
6 | false
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | runtime; build; native; contentfiles; analyzers; buildtransitive
15 | all
16 |
17 |
18 | runtime; build; native; contentfiles; analyzers; buildtransitive
19 | all
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/EHentaiAPI.UnitTest/Gallery.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client;
2 | using EHentaiAPI.Client.Data;
3 | using EHentaiAPI.Client.Parser;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 | using Xunit;
10 | using Xunit.Extensions.Ordering;
11 |
12 | [assembly: TestCaseOrderer("Xunit.Extensions.Ordering.TestCaseOrderer", "Xunit.Extensions.Ordering")]
13 | namespace EHentaiAPI.UnitTest
14 | {
15 | public class Gallery : IClassFixture
16 | {
17 | private readonly EhClient client;
18 | private const string myLove = "https://e-hentai.org/g/2062067/588c82702b/";
19 |
20 | public Gallery(ShareClient shareClient)
21 | {
22 | client = shareClient.Client;
23 |
24 | client.Cookies.Add(new System.Net.Cookie("sl", "dm_1", "/", "e-hentai.org"));
25 | client.Settings.GallerySite = Settings.GallerySites.SITE_E;
26 | }
27 |
28 | [Fact, Order(1)]
29 | public async void GetGalleryList()
30 | {
31 | var req = new EhRequest();
32 | req.SetArgs("https://e-hentai.org");
33 | req.SetMethod(EhClient.Method.METHOD_GET_GALLERY_LIST);
34 |
35 | var result = await client.Execute(req);
36 |
37 | Assert.NotEmpty(result.galleryInfoList);
38 | }
39 |
40 | [Fact, Order(2)]
41 | public async void GetGalleryDetail()
42 | {
43 | var info = await client.GetGalleryDetailAsync(myLove);
44 |
45 | Assert.Equal("2021-11-16 15:10", info.Posted);
46 | Assert.Equal("Yes", info.Visible);
47 | Assert.Equal("https://e-hentai.org/g/2056718/d0e47e5e3e/", info.Parent);
48 | Assert.Equal("[Pixiv] Miwabe Sakura (4816744)", info.Title);
49 | Assert.Equal("[Pixiv] みわべさくら (4816744)", info.TitleJpn);
50 | Assert.Equal("miwabe sakura", info.Tags.FirstOrDefault(x => x.TagGroupName == "artist")?.GetTagAt(0));
51 |
52 | Assert.NotEmpty(info.Comments.Comments);
53 | Assert.Equal("Pokom", info.Comments.Comments.FirstOrDefault()?.User);
54 | Assert.True(info.Comments.Comments.FirstOrDefault()?.Uploader);
55 | Assert.True(info.Comments.Comments.FirstOrDefault()?.Comment.Length > 0);
56 | }
57 |
58 | [Fact, Order(3)]
59 | public async void GetTorrentList()
60 | {
61 | var info = await client.GetGalleryDetailAsync("https://e-hentai.org/g/2062872/fb6abc76c6/");
62 | Assert.NotEmpty(await client.GetTorrentListAsync(info));
63 | }
64 |
65 | [Fact, Order(3)]
66 | public async void GetArchiveList()
67 | {
68 | await client.SignInAsync(TestSettings.UserName, TestSettings.Password);
69 | var info = await client.GetGalleryDetailAsync("https://e-hentai.org/g/2062872/fb6abc76c6/");
70 | var archiveList = await client.GetArchiveListAsync(info);
71 | Assert.False(string.IsNullOrWhiteSpace(archiveList.Key));
72 | Assert.NotEmpty(archiveList.Value);
73 | }
74 |
75 | [Fact, Order(3)]
76 | public async void GetGalleryToken()
77 | {
78 | Assert.Equal("03037d8698", await client.GetGalleryTokenAsync("https://e-hentai.org/s/35142216f7/2062874-16"));
79 | }
80 |
81 | [Fact, Order(3)]
82 | public async void GetGalleryPreviewSet()
83 | {
84 | var info = await client.GetGalleryDetailAsync("https://e-hentai.org/g/2062872/fb6abc76c6/");
85 | Assert.True((await client.GetPreviewSetAsync(info, 0)).Key.Size > 0);
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/EHentaiAPI.UnitTest/Login.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client;
2 | using System;
3 | using Xunit;
4 | using Xunit.Extensions.Ordering;
5 | using Xunit.Sdk;
6 |
7 | namespace EHentaiAPI.UnitTest
8 | {
9 | public class LoginTest : IClassFixture
10 | {
11 | private EhClient client;
12 |
13 | public LoginTest(ShareClient shareClient)
14 | {
15 | client = shareClient.Client;
16 | }
17 |
18 | private void CheckSettings()
19 | {
20 | //please fill your username/password into TestSettings.
21 | Assert.False(string.IsNullOrWhiteSpace(TestSettings.UserName));
22 | Assert.False(string.IsNullOrWhiteSpace(TestSettings.Password));
23 | }
24 |
25 | [Fact, Order(1)]
26 | public async void SignIn()
27 | {
28 | CheckSettings();
29 |
30 | var userName = await client.SignInAsync(TestSettings.UserName, TestSettings.Password);
31 | Assert.Equal(TestSettings.UserName, userName);
32 | }
33 |
34 | [Fact, Order(2)]
35 | public async void GetProfile()
36 | {
37 | var userName = await client.SignInAsync(TestSettings.UserName, TestSettings.Password);
38 | var profile = await client.GetProfileAsync();
39 | Assert.Equal(userName, profile.displayName);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/EHentaiAPI.UnitTest/ShareClient.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.UnitTest
9 | {
10 | public class ShareClient : IDisposable
11 | {
12 | public EhClient Client { get; set; } = new EhClient();
13 |
14 | public void Dispose()
15 | {
16 |
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/EHentaiAPI.UnitTest/TestSetting.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.UnitTest
8 | {
9 | public static partial class TestSettings
10 | {
11 | public static string UserName { get; set; }
12 | public static string Password { get; set; }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/EHentaiAPI.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31829.152
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EHentaiAPI", "EHentaiAPI\EHentaiAPI.csproj", "{8573987F-0C09-420D-8980-54231064F003}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EHentaiAPI.TestConsole", "EHentaiAPI.TestConsole\EHentaiAPI.TestConsole.csproj", "{280B945F-786E-4A2B-94D0-27AEAA47AF35}"
9 | EndProject
10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EHentaiAPI.UnitTest", "EHentaiAPI.UnitTest\EHentaiAPI.UnitTest.csproj", "{25B75B89-34F5-4B51-80A4-2F06E604C2AF}"
11 | EndProject
12 | Global
13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
14 | Debug|Any CPU = Debug|Any CPU
15 | Release|Any CPU = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
18 | {8573987F-0C09-420D-8980-54231064F003}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
19 | {8573987F-0C09-420D-8980-54231064F003}.Debug|Any CPU.Build.0 = Debug|Any CPU
20 | {8573987F-0C09-420D-8980-54231064F003}.Release|Any CPU.ActiveCfg = Release|Any CPU
21 | {8573987F-0C09-420D-8980-54231064F003}.Release|Any CPU.Build.0 = Release|Any CPU
22 | {280B945F-786E-4A2B-94D0-27AEAA47AF35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
23 | {280B945F-786E-4A2B-94D0-27AEAA47AF35}.Debug|Any CPU.Build.0 = Debug|Any CPU
24 | {280B945F-786E-4A2B-94D0-27AEAA47AF35}.Release|Any CPU.ActiveCfg = Release|Any CPU
25 | {280B945F-786E-4A2B-94D0-27AEAA47AF35}.Release|Any CPU.Build.0 = Release|Any CPU
26 | {25B75B89-34F5-4B51-80A4-2F06E604C2AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
27 | {25B75B89-34F5-4B51-80A4-2F06E604C2AF}.Debug|Any CPU.Build.0 = Debug|Any CPU
28 | {25B75B89-34F5-4B51-80A4-2F06E604C2AF}.Release|Any CPU.ActiveCfg = Release|Any CPU
29 | {25B75B89-34F5-4B51-80A4-2F06E604C2AF}.Release|Any CPU.Build.0 = Release|Any CPU
30 | EndGlobalSection
31 | GlobalSection(SolutionProperties) = preSolution
32 | HideSolutionNode = FALSE
33 | EndGlobalSection
34 | GlobalSection(ExtensibilityGlobals) = postSolution
35 | SolutionGuid = {65763CB1-EB41-42D7-9190-FCEC104BC864}
36 | EndGlobalSection
37 | EndGlobal
38 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/FavListUrlBuilder.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.ExtendFunction;
2 | using EHentaiAPI.Utils;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Net;
7 | using System.Text;
8 | using System.Text.Encodings.Web;
9 | using System.Threading.Tasks;
10 |
11 | namespace EHentaiAPI.Client.Data
12 | {
13 | public class FavListUrlBuilder
14 | {
15 | public const int FAV_CAT_ALL = -1;
16 |
17 | private const string TAG = nameof(FavListUrlBuilder);
18 | private readonly EhUrl ehUrl;
19 | public int Index { get; set; }
20 |
21 | public string Keyword { get; set; }
22 | public int FavCat { get; set; } = FAV_CAT_ALL;
23 |
24 | public FavListUrlBuilder(EhUrl ehUrl)
25 | {
26 | this.ehUrl = ehUrl;
27 | }
28 |
29 | public static bool IsValidFavCat(int favCat)
30 | {
31 | return favCat >= 0 && favCat <= 9;
32 | }
33 |
34 | public string Build()
35 | {
36 | var ub = new UrlBuilder(ehUrl.GetFavoritesUrl());
37 | if (IsValidFavCat(FavCat))
38 | {
39 | ub.AddQuery("favcat", FavCat.ToString());
40 | }
41 | else if (FavCat == FAV_CAT_ALL)
42 | {
43 | ub.AddQuery("favcat", "all");
44 | }
45 | if (!string.IsNullOrWhiteSpace(Keyword))
46 | {
47 | try
48 | {
49 | ub.AddQuery("f_search", WebUtility.UrlEncode(Keyword));
50 | // Name
51 | ub.AddQuery("sn", "on");
52 | // Tags
53 | ub.AddQuery("st", "on");
54 | // Note
55 | ub.AddQuery("sf", "on");
56 | }
57 | catch (Exception)
58 | {
59 | Log.E(TAG, $"Can't URLEncoder.encode {Keyword} or can't add queries.");
60 | }
61 | }
62 | if (Index > 0)
63 | {
64 | ub.AddQuery("page", Index);
65 | }
66 | return ub.Build();
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/GalleryComment.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client.Data
8 | {
9 | public class GalleryComment
10 | {
11 | // 0 for uploader comment. can't vote
12 | public long Id { get; set; }
13 | public int Score { get; set; }
14 | public bool Editable { get; set; }
15 | public bool VoteUpAble { get; set; }
16 | public bool VoteUpEd { get; set; }
17 | public bool VoteDownAble { get; set; }
18 | public bool VoteDownEd { get; set; }
19 | public bool Uploader { get; set; }
20 | public string VoteState { get; set; }
21 | public long Time { get; set; }
22 | public string User { get; set; }
23 | public string Comment { get; set; }
24 | public long LastEdited { get; set; }
25 |
26 | public override string ToString() => $"[{Score}]{User}:{Comment}";
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/GalleryCommentList.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client.Data
8 | {
9 | public class GalleryCommentList
10 | {
11 | public GalleryComment[] Comments { get; set; }
12 | public bool HasMore { get; set; }
13 |
14 | public GalleryCommentList(GalleryComment[] comments, bool hasMore)
15 | {
16 | this.Comments = comments;
17 | this.HasMore = hasMore;
18 | }
19 |
20 | public override string ToString() => $"{Comments?.Length} comments {(HasMore ? "(...has more)" : "")}";
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/GalleryDetail.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client.Data
8 | {
9 | public class GalleryDetail : GalleryInfo
10 | {
11 | public long ApiUid { get; set; } = -1L;
12 | public string ApiKey { get; set; }
13 | public int TorrentCount { get; set; }
14 | public string TorrentUrl { get; set; }
15 | public string ArchiveUrl { get; set; }
16 | public string Parent { get; set; }
17 | public List NewerVersions { get; set; } = new();
18 | public string Visible { get; set; }
19 | public string Language { get; set; }
20 | public string Size { get; set; }
21 | public int FavoriteCount { get; set; }
22 | public bool IsFavorited { get; set; }
23 | public int RatingCount { get; set; }
24 | public GalleryTagGroup[] Tags { get; set; }
25 | public GalleryCommentList Comments { get; set; }
26 | public int PreviewPages { get; set; }
27 | public PreviewSet PreviewSet { get; set; }
28 | public string Url { get; set; }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/GalleryInfo.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading;
7 | using System.Threading.Tasks;
8 |
9 | namespace EHentaiAPI.Client.Data
10 | {
11 | public class GalleryInfo
12 | {
13 | /**
14 | * ISO 639-1
15 | */
16 | public const string S_LANG_JA = "JA";
17 | public const string S_LANG_EN = "EN";
18 | public const string S_LANG_ZH = "ZH";
19 | public const string S_LANG_NL = "NL";
20 | public const string S_LANG_FR = "FR";
21 | public const string S_LANG_DE = "DE";
22 | public const string S_LANG_HU = "HU";
23 | public const string S_LANG_IT = "IT";
24 | public const string S_LANG_KO = "KO";
25 | public const string S_LANG_PL = "PL";
26 | public const string S_LANG_PT = "PT";
27 | public const string S_LANG_RU = "RU";
28 | public const string S_LANG_ES = "ES";
29 | public const string S_LANG_TH = "TH";
30 | public const string S_LANG_VI = "VI";
31 |
32 | public readonly static string[] S_LANGS = {
33 | S_LANG_EN,
34 | S_LANG_ZH,
35 | S_LANG_ES,
36 | S_LANG_KO,
37 | S_LANG_RU,
38 | S_LANG_FR,
39 | S_LANG_PT,
40 | S_LANG_TH,
41 | S_LANG_DE,
42 | S_LANG_IT,
43 | S_LANG_VI,
44 | S_LANG_PL,
45 | S_LANG_HU,
46 | S_LANG_NL,
47 | };
48 |
49 | public readonly static Regex[] S_LANG_PATTERNS = {
50 | new Regex("[(\\[]eng(?:lish)?[)\\]]|英訳", RegexOptions.IgnoreCase),
51 | // [((\[]ch(?:inese)?[))\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳
52 | new Regex("[(\uFF08\\[]ch(?:inese)?[)\uFF09\\]]|[汉漢]化|中[国國][语語]|中文|中国翻訳", RegexOptions.IgnoreCase),
53 | new Regex("[(\\[]spanish[)\\]]|[(\\[]Español[)\\]]|スペイン翻訳", RegexOptions.IgnoreCase),
54 | new Regex("[(\\[]korean?[)\\]]|韓国翻訳", RegexOptions.IgnoreCase),
55 | new Regex("[(\\[]rus(?:sian)?[)\\]]|ロシア翻訳", RegexOptions.IgnoreCase),
56 | new Regex("[(\\[]fr(?:ench)?[)\\]]|フランス翻訳", RegexOptions.IgnoreCase),
57 | new Regex("[(\\[]portuguese|ポルトガル翻訳", RegexOptions.IgnoreCase),
58 | new Regex("[(\\[]thai(?: ภาษาไทย)?[)\\]]|แปลไทย|タイ翻訳", RegexOptions.IgnoreCase),
59 | new Regex("[(\\[]german[)\\]]|ドイツ翻訳", RegexOptions.IgnoreCase),
60 | new Regex("[(\\[]italiano?[)\\]]|イタリア翻訳", RegexOptions.IgnoreCase),
61 | new Regex("[(\\[]vietnamese(?: Tiếng Việt)?[)\\]]|ベトナム翻訳", RegexOptions.IgnoreCase),
62 | new Regex("[(\\[]polish[)\\]]|ポーランド翻訳", RegexOptions.IgnoreCase),
63 | new Regex("[(\\[]hun(?:garian)?[)\\]]|ハンガリー翻訳", RegexOptions.IgnoreCase),
64 | new Regex("[(\\[]dutch[)\\]]|オランダ翻訳", RegexOptions.IgnoreCase),
65 | };
66 |
67 | public readonly static string[] S_LANG_TAGS = {
68 | "language:english",
69 | "language:chinese",
70 | "language:spanish",
71 | "language:korean",
72 | "language:russian",
73 | "language:french",
74 | "language:portuguese",
75 | "language:thai",
76 | "language:german",
77 | "language:italian",
78 | "language:vietnamese",
79 | "language:polish",
80 | "language:hungarian",
81 | "language:dutch",
82 | };
83 |
84 | public long Gid { get; set; }
85 | public string Token { get; set; }
86 | public string Title { get; set; }
87 | public string TitleJpn { get; set; }
88 | public string Thumb { get; set; }
89 | public int Category { get; set; }
90 | public string Posted { get; set; }
91 | public string Uploader { get; set; }
92 | public float Rating { get; set; }
93 | public bool Rated { get; set; }
94 | public string[] SimpleTags { get; set; }
95 | public int Pages { get; set; }
96 | public int ThumbWidth { get; set; }
97 | public int ThumbHeight { get; set; }
98 | public int SpanSize { get; set; }
99 | public int SpanIndex { get; set; }
100 | public int SpanGroupIndex { get; set; }
101 |
102 | public string AvaliableTitle => string.IsNullOrWhiteSpace(TitleJpn) ? Title : TitleJpn;
103 |
104 | public string SimpleLanguage { get; set; }
105 | public int FavoriteSlot { get; set; } = -2;
106 | public string FavoriteName { get; set; }
107 |
108 | public void GenerateSLang()
109 | {
110 | if (SimpleTags != null)
111 | {
112 | GenerateSLangFromTags();
113 | }
114 | if (SimpleLanguage == null && Title != null)
115 | {
116 | GenerateSLangFromTitle();
117 | }
118 | }
119 |
120 | private void GenerateSLangFromTags()
121 | {
122 | foreach (string tag in SimpleTags)
123 | {
124 | for (int i = 0; i < S_LANGS.Length; i++)
125 | {
126 | if (S_LANG_TAGS[i].Equals(tag))
127 | {
128 | SimpleLanguage = S_LANGS[i];
129 | return;
130 | }
131 | }
132 | }
133 | }
134 |
135 | private void GenerateSLangFromTitle()
136 | {
137 | for (int i = 0; i < S_LANGS.Length; i++)
138 | {
139 | if (S_LANG_PATTERNS[i].Match(Title)?.Success ?? false)
140 | {
141 | SimpleLanguage = S_LANGS[i];
142 | return;
143 | }
144 | }
145 | SimpleLanguage = null;
146 | }
147 |
148 | public override string ToString() => $"{this.Gid} {TitleJpn ?? Title}";
149 | }
150 | }
151 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/GalleryPreview.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client.Data
8 | {
9 | public class GalleryPreview
10 | {
11 | public string ImageKey { get; set; }
12 | public string ImageUrl { get; set; }
13 | public string PageUrl { get; set; }
14 | public int Position { get; set; }
15 |
16 | //Added by EHentaiAPI
17 | public string PToken { get; set; }
18 |
19 | public override string ToString() => $"[{Position}]{ImageUrl}";
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/GalleryTagGroup.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client.Data
8 | {
9 | public class GalleryTagGroup
10 | {
11 | public List TagList { get; set; } = new List();
12 | public string TagGroupName { get; set; }
13 |
14 | public int Size => TagList.Count;
15 | public string GetTagAt(int index) => TagList.ElementAtOrDefault(index);
16 | public void AddTag(string tag) => TagList.Add(tag);
17 |
18 | public override string ToString() => $"{TagGroupName} ({Size})";
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/LargePreviewSet.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Parser;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Client.Data
9 | {
10 | public class LargePreviewSet : PreviewSet
11 | {
12 | public List PositionList { get; private set; }
13 | public List ImageUrlList { get; private set; }
14 | public List PageUrlList { get; private set; }
15 |
16 | public LargePreviewSet()
17 | {
18 | PositionList = new();
19 | ImageUrlList = new();
20 | PageUrlList = new();
21 | }
22 |
23 | public void AddItem(int index, string imageUrl, string pageUrl)
24 | {
25 | PositionList.Add(index);
26 | ImageUrlList.Add(imageUrl);
27 | PageUrlList.Add(pageUrl);
28 | }
29 |
30 | public override int Size => ImageUrlList.Count;
31 |
32 | public override int GetPosition(int index)
33 | {
34 | return PositionList[index];
35 | }
36 |
37 | public override string GetPageUrlAt(int index)
38 | {
39 | return PageUrlList[index];
40 | }
41 |
42 | public override GalleryPreview GetGalleryPreview(long gid, int index)
43 | {
44 | var galleryPreview = new GalleryPreview();
45 | galleryPreview.Position = PositionList[index];
46 | galleryPreview.ImageKey = EhCacheKeyFactory.GetLargePreviewKey(gid, galleryPreview.Position);
47 | galleryPreview.ImageUrl = ImageUrlList[index];
48 | galleryPreview.PageUrl = PageUrlList[index];
49 | galleryPreview.PToken = GalleryPageUrlParser.Parse(galleryPreview.PageUrl).pToken;
50 | return galleryPreview;
51 | }
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/ListUrlBuilder.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Utils;
2 | using System;
3 | using System.Net;
4 | using System.Text;
5 |
6 | namespace EHentaiAPI.Client.Data
7 | {
8 | public class ListUrlBuilder
9 | {
10 | public static class AdvanceSearchTable
11 | {
12 | public const int SNAME = 0x1;
13 | public const int STAGS = 0x2;
14 | public const int SDESC = 0x4;
15 | public const int STORR = 0x8;
16 | public const int STO = 0x10;
17 | public const int SDT1 = 0x20;
18 | public const int SDT2 = 0x40;
19 | public const int SH = 0x80;
20 | public const int SFL = 0x100;
21 | public const int SFU = 0x200;
22 | public const int SFT = 0x400;
23 | }
24 |
25 | public enum ListMode
26 | {
27 | Normal = 0x0,
28 | Uploader = 0x1,
29 | Tag = 0x2,
30 | WhatsHot = 0x3,
31 | ImageSearch = 0x4,
32 | SubScription = 0x5,
33 | TopList = 0x6
34 | }
35 |
36 | private readonly EhUrl ehUrl;
37 |
38 | public ListMode Mode { get; set; } = ListMode.Normal;
39 | public int PageIndex { get; set; } = 0;
40 | public int Category { get; set; } = EhUtils.NONE;
41 |
42 | private string keyword;
43 | public string Keyword
44 | {
45 | get
46 | {
47 | return ListMode.Uploader == Mode ? "uploader:" + keyword : keyword;
48 | }
49 | set
50 | {
51 | keyword = value;
52 | }
53 | }
54 |
55 | public string SHash { get; set; } = null;
56 |
57 | public int AdvanceSearch { get; set; } = -1;
58 | public int MinRating { get; set; } = -1;
59 | public int PageFrom { get; set; } = -1;
60 | public int PageTo { get; set; } = -1;
61 |
62 | public string ImagePath { get; set; }
63 | public bool UseSimilarityScan { get; set; }
64 | public bool OnlySearchCovers { get; set; }
65 | public bool ShowExpunged { get; set; }
66 |
67 | public ListUrlBuilder(EhUrl ehUrl)
68 | {
69 | this.ehUrl = ehUrl;
70 | }
71 |
72 | /**
73 | * Make this ListUrlBuilder point to homepage
74 | */
75 | public void Reset()
76 | {
77 | Mode = ListMode.Normal;
78 | PageIndex = 0;
79 | Category = EhUtils.NONE;
80 | Keyword = null;
81 | AdvanceSearch = -1;
82 | MinRating = -1;
83 | PageFrom = -1;
84 | PageTo = -1;
85 | ImagePath = null;
86 | UseSimilarityScan = false;
87 | OnlySearchCovers = false;
88 | ShowExpunged = false;
89 | SHash = null;
90 | }
91 |
92 | /**
93 | * @param query xxx=yyy&mmm=nnn
94 | */
95 | // TODO page
96 | public void SetQuery(string query)
97 | {
98 | Reset();
99 |
100 | if (string.IsNullOrWhiteSpace(query))
101 | {
102 | return;
103 | }
104 | var querys = query.Split('&');
105 | int category = 0;
106 | string keyword = null;
107 | bool enableAdvanceSearch = false;
108 | int advanceSearch = 0;
109 | bool enableMinRating = false;
110 | int minRating = -1;
111 | bool enablePage = false;
112 | int pageFrom = -1;
113 | int pageTo = -1;
114 | foreach (string str in querys)
115 | {
116 | int index = str.IndexOf('=');
117 | if (index < 0)
118 | continue;
119 | var key = str.Substring(0, index - 0);
120 | var value = str.Substring(index + 1);
121 | switch (key)
122 | {
123 | case "f_cats":
124 | var cats = int.TryParse(value, out var d) ? d : EhConfig.ALL_CATEGORY;
125 | category |= (~cats) & EhConfig.ALL_CATEGORY;
126 | break;
127 | case "f_doujinshi":
128 | if ("1".Equals(value))
129 | {
130 | category |= EhConfig.DOUJINSHI;
131 | }
132 | break;
133 | case "f_manga":
134 | if ("1".Equals(value))
135 | {
136 | category |= EhConfig.MANGA;
137 | }
138 | break;
139 | case "f_artistcg":
140 | if ("1".Equals(value))
141 | {
142 | category |= EhConfig.ARTIST_CG;
143 | }
144 | break;
145 | case "f_gamecg":
146 | if ("1".Equals(value))
147 | {
148 | category |= EhConfig.GAME_CG;
149 | }
150 | break;
151 | case "f_western":
152 | if ("1".Equals(value))
153 | {
154 | category |= EhConfig.WESTERN;
155 | }
156 | break;
157 | case "f_non-h":
158 | if ("1".Equals(value))
159 | {
160 | category |= EhConfig.NON_H;
161 | }
162 | break;
163 | case "f_imageset":
164 | if ("1".Equals(value))
165 | {
166 | category |= EhConfig.IMAGE_SET;
167 | }
168 | break;
169 | case "f_cosplay":
170 | if ("1".Equals(value))
171 | {
172 | category |= EhConfig.COSPLAY;
173 | }
174 | break;
175 | case "f_asianporn":
176 | if ("1".Equals(value))
177 | {
178 | category |= EhConfig.ASIAN_PORN;
179 | }
180 | break;
181 | case "f_misc":
182 | if ("1".Equals(value))
183 | {
184 | category |= EhConfig.MISC;
185 | }
186 | break;
187 | case "f_search":
188 | keyword = WebUtility.UrlDecode(value);
189 |
190 | break;
191 | case "advsearch":
192 | if ("1".Equals(value))
193 | {
194 | enableAdvanceSearch = true;
195 | }
196 | break;
197 | case "f_sname":
198 | if ("on".Equals(value))
199 | {
200 | advanceSearch |= AdvanceSearchTable.SNAME;
201 | }
202 | break;
203 | case "f_stags":
204 | if ("on".Equals(value))
205 | {
206 | advanceSearch |= AdvanceSearchTable.STAGS;
207 | }
208 | break;
209 | case "f_sdesc":
210 | if ("on".Equals(value))
211 | {
212 | advanceSearch |= AdvanceSearchTable.SDESC;
213 | }
214 | break;
215 | case "f_storr":
216 | if ("on".Equals(value))
217 | {
218 | advanceSearch |= AdvanceSearchTable.STORR;
219 | }
220 | break;
221 | case "f_sto":
222 | if ("on".Equals(value))
223 | {
224 | advanceSearch |= AdvanceSearchTable.STO;
225 | }
226 | break;
227 | case "f_sdt1":
228 | if ("on".Equals(value))
229 | {
230 | advanceSearch |= AdvanceSearchTable.SDT1;
231 | }
232 | break;
233 | case "f_sdt2":
234 | if ("on".Equals(value))
235 | {
236 | advanceSearch |= AdvanceSearchTable.SDT2;
237 | }
238 | break;
239 | case "f_sh":
240 | if ("on".Equals(value))
241 | {
242 | advanceSearch |= AdvanceSearchTable.SH;
243 | }
244 | break;
245 | case "f_sfl":
246 | if ("on".Equals(value))
247 | {
248 | advanceSearch |= AdvanceSearchTable.SFL;
249 | }
250 | break;
251 | case "f_sfu":
252 | if ("on".Equals(value))
253 | {
254 | advanceSearch |= AdvanceSearchTable.SFU;
255 | }
256 | break;
257 | case "f_sft":
258 | if ("on".Equals(value))
259 | {
260 | advanceSearch |= AdvanceSearchTable.SFT;
261 | }
262 | break;
263 | case "f_sr":
264 | if ("on".Equals(value))
265 | {
266 | enableMinRating = true;
267 | }
268 | break;
269 | case "f_srdd":
270 | minRating = int.TryParse(value, out d) ? d : -1;
271 | break;
272 | case "f_sp":
273 | if ("on".Equals(value))
274 | {
275 | enablePage = true;
276 | }
277 | break;
278 | case "f_spf":
279 | pageFrom = int.TryParse(value, out d) ? d : -1;
280 | break;
281 | case "f_spt":
282 | pageTo = int.TryParse(value, out d) ? d : -1;
283 | break;
284 | case "f_shash":
285 | SHash = value;
286 | break;
287 | }
288 | }
289 |
290 | Category = category;
291 | Keyword = keyword;
292 | if (enableAdvanceSearch)
293 | {
294 | AdvanceSearch = advanceSearch;
295 | if (enableMinRating)
296 | {
297 | MinRating = minRating;
298 | }
299 | else
300 | {
301 | MinRating = -1;
302 | }
303 | if (enablePage)
304 | {
305 | PageFrom = pageFrom;
306 | PageTo = pageTo;
307 | }
308 | else
309 | {
310 | PageFrom = -1;
311 | PageTo = -1;
312 | }
313 | }
314 | else
315 | {
316 | AdvanceSearch = -1;
317 | }
318 | }
319 |
320 | public string Build()
321 | {
322 | StringBuilder sb;
323 |
324 | switch (Mode)
325 | {
326 | default:
327 | case ListMode.Normal:
328 | case ListMode.SubScription:
329 | {
330 | string url;
331 | if (Mode == ListMode.Normal)
332 | {
333 | url = ehUrl.GetHost();
334 | }
335 | else
336 | {
337 | url = ehUrl.GetWatchedUrl();
338 | }
339 |
340 | var ub = new UrlBuilder(url);
341 | if (Category != EhUtils.NONE)
342 | {
343 | ub.AddQuery("f_cats", (~Category) & EhConfig.ALL_CATEGORY);
344 | }
345 | // Search key
346 | if (Keyword != null)
347 | {
348 | string keyword = Keyword.Trim();
349 | if (!string.IsNullOrWhiteSpace(keyword))
350 | {
351 | try
352 | {
353 | ub.AddQuery("f_search", WebUtility.UrlEncode(Keyword));
354 | }
355 | catch
356 | {
357 | // Empty
358 | }
359 | }
360 | }
361 | if (SHash != null)
362 | {
363 | ub.AddQuery("f_shash", SHash);
364 | }
365 | // Page index
366 | if (PageIndex != 0)
367 | {
368 | ub.AddQuery("page", PageIndex);
369 | }
370 | // Advance search
371 | if (AdvanceSearch != -1)
372 | {
373 | ub.AddQuery("advsearch", "1");
374 | if ((AdvanceSearch & AdvanceSearchTable.SNAME) != 0)
375 | ub.AddQuery("f_sname", "on");
376 | if ((AdvanceSearch & AdvanceSearchTable.STAGS) != 0)
377 | ub.AddQuery("f_stags", "on");
378 | if ((AdvanceSearch & AdvanceSearchTable.SDESC) != 0)
379 | ub.AddQuery("f_sdesc", "on");
380 | if ((AdvanceSearch & AdvanceSearchTable.STORR) != 0)
381 | ub.AddQuery("f_storr", "on");
382 | if ((AdvanceSearch & AdvanceSearchTable.STO) != 0) ub.AddQuery("f_sto", "on");
383 | if ((AdvanceSearch & AdvanceSearchTable.SDT1) != 0)
384 | ub.AddQuery("f_sdt1", "on");
385 | if ((AdvanceSearch & AdvanceSearchTable.SDT2) != 0)
386 | ub.AddQuery("f_sdt2", "on");
387 | if ((AdvanceSearch & AdvanceSearchTable.SH) != 0) ub.AddQuery("f_sh", "on");
388 | if ((AdvanceSearch & AdvanceSearchTable.SFL) != 0) ub.AddQuery("f_sfl", "on");
389 | if ((AdvanceSearch & AdvanceSearchTable.SFU) != 0) ub.AddQuery("f_sfu", "on");
390 | if ((AdvanceSearch & AdvanceSearchTable.SFT) != 0) ub.AddQuery("f_sft", "on");
391 | // Set min star
392 | if (MinRating != -1)
393 | {
394 | ub.AddQuery("f_sr", "on");
395 | ub.AddQuery("f_srdd", MinRating);
396 | }
397 | // Pages
398 | if (PageFrom != -1 || PageTo != -1)
399 | {
400 | ub.AddQuery("f_sp", "on");
401 | ub.AddQuery("f_spf", PageFrom != -1 ? PageFrom : "");
402 | ub.AddQuery("f_spt", PageTo != -1 ? PageTo : "");
403 | }
404 | }
405 | return ub.Build();
406 | }
407 | case ListMode.Uploader:
408 | {
409 | sb = new StringBuilder(ehUrl.GetHost());
410 | sb.Append("uploader/");
411 | try
412 | {
413 | sb.Append(WebUtility.UrlEncode(Keyword));
414 | }
415 | catch
416 | {
417 | // Empty
418 | }
419 | if (PageIndex != 0)
420 | {
421 | sb.Append('/').Append(PageIndex);
422 | }
423 | return sb.ToString();
424 | }
425 | case ListMode.Tag:
426 | {
427 | sb = new StringBuilder(ehUrl.GetHost());
428 | sb.Append("tag/");
429 | try
430 | {
431 | sb.Append(WebUtility.UrlEncode(Keyword));
432 | }
433 | catch
434 | {
435 | // Empty
436 | }
437 | if (PageIndex != 0)
438 | {
439 | sb.Append('/').Append(PageIndex);
440 | }
441 | return sb.ToString();
442 | }
443 | case ListMode.WhatsHot:
444 | return ehUrl.GetPopularUrl();
445 | case ListMode.ImageSearch:
446 | return ehUrl.GetImageSearchUrl();
447 | case ListMode.TopList:
448 | sb = new StringBuilder(EhUrl.HOST_E);
449 | sb.Append("toplist.php?tl=");
450 | try
451 | {
452 | sb.Append(WebUtility.UrlEncode(Keyword));
453 | }
454 | catch
455 | {
456 | // Empty
457 | }
458 | if (PageIndex != 0)
459 | {
460 | sb.Append("&p=").Append(PageIndex);
461 | }
462 | return sb.ToString();
463 | }
464 | }
465 | }
466 | }
467 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/NormalPreviewSet.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Parser;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Client.Data
9 | {
10 | public class NormalPreviewSet : PreviewSet
11 | {
12 | public List PositionList { get; private set; } = new();
13 | public List ImageKeyList { get; private set; } = new();
14 | public List ImageUrlList { get; private set; } = new();
15 | public List PageUrlList { get; private set; } = new();
16 |
17 | private static string GetImageKey(string imageUrl)
18 | {
19 | int index = imageUrl.IndexOf('/');
20 | if (index >= 0)
21 | {
22 | return imageUrl.Substring(index + 1);
23 | }
24 | else
25 | {
26 | return imageUrl;
27 | }
28 | }
29 |
30 | public void AddItem(int position, string imageUrl, string pageUrl)
31 | {
32 | PositionList.Add(position);
33 | ImageKeyList.Add(GetImageKey(imageUrl));
34 | ImageUrlList.Add(imageUrl);
35 | PageUrlList.Add(pageUrl);
36 | }
37 |
38 | public override int Size => PositionList.Count;
39 |
40 | public override int GetPosition(int index)
41 | {
42 | return PositionList[index];
43 | }
44 |
45 | public override string GetPageUrlAt(int index)
46 | {
47 | return PageUrlList[index];
48 | }
49 |
50 | public override GalleryPreview GetGalleryPreview(long gid, int index)
51 | {
52 | var galleryPreview = new GalleryPreview();
53 | galleryPreview.Position = PositionList[index];
54 | galleryPreview.ImageKey = ImageKeyList[index];
55 | galleryPreview.ImageUrl = ImageUrlList[index];
56 | galleryPreview.PageUrl = PageUrlList[index];
57 | galleryPreview.PToken = GalleryPageUrlParser.Parse(galleryPreview.PageUrl).pToken;
58 |
59 | return galleryPreview;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Data/PreviewSet.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client.Data
8 | {
9 | public abstract class PreviewSet
10 | {
11 | public abstract int Size { get; }
12 |
13 | public abstract int GetPosition(int index);
14 |
15 | public abstract string GetPageUrlAt(int index);
16 |
17 | public abstract GalleryPreview GetGalleryPreview(long gid, int index);
18 |
19 | public override string ToString() => $"{Size} images";
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/EhCacheKeyFactory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client
8 | {
9 | public static class EhCacheKeyFactory
10 | {
11 | public static string GetThumbKey(long gid)
12 | {
13 | return "preview:large:" + gid + ":" + 0; // "thumb:" + gid;
14 | }
15 |
16 | public static string GetNormalPreviewKey(long gid, int index)
17 | {
18 | return "preview:normal:" + gid + ":" + index;
19 | }
20 |
21 | public static string GetLargePreviewKey(long gid, int index)
22 | {
23 | return "preview:large:" + gid + ":" + index;
24 | }
25 |
26 | public static string GetLargePreviewSetKey(long gid, int index)
27 | {
28 | return "large_preview_set:" + gid + ":" + index;
29 | }
30 |
31 | public static string GetImageKey(long gid, int index)
32 | {
33 | return "image:" + gid + ":" + index;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/EhClient.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Data;
2 | using EHentaiAPI.Client.Exceptions;
3 | using EHentaiAPI.Client.Parser;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Net;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace EHentaiAPI.Client
11 | {
12 | public class EhClient
13 | {
14 | public CookieContainer Cookies { get; set; } = new CookieContainer();
15 |
16 | private EhUrl ehUrl;
17 | public EhUrl EhUrl
18 | {
19 | get
20 | {
21 | if (ehUrl is null)
22 | {
23 | throw new EhException("Please setup EhClient::Settings before using EhClient::EhUrl");
24 | }
25 | return ehUrl;
26 | }
27 | }
28 |
29 | private Settings setting;
30 | public Settings Settings
31 | {
32 | get
33 | {
34 | return setting;
35 | }
36 | set
37 | {
38 | setting = value;
39 | ehUrl = new EhUrl(value);
40 | }
41 | }
42 |
43 | public EhClient(Settings setting = default)
44 | {
45 | Settings = setting ?? new Settings();
46 | }
47 |
48 | public enum Method
49 | {
50 | METHOD_SIGN_IN,
51 | METHOD_GET_GALLERY_LIST,
52 | METHOD_GET_GALLERY_DETAIL,
53 | METHOD_GET_PREVIEW_SET,
54 | METHOD_GET_RATE_GALLERY,
55 | METHOD_GET_COMMENT_GALLERY,
56 | METHOD_GET_GALLERY_TOKEN,
57 | METHOD_GET_FAVORITES,
58 | METHOD_ADD_FAVORITES,
59 | METHOD_ADD_FAVORITES_RANGE,
60 | METHOD_MODIFY_FAVORITES,
61 | METHOD_GET_TORRENT_LIST,
62 | METHOD_GET_PROFILE,
63 | METHOD_VOTE_COMMENT,
64 | METHOD_IMAGE_SEARCH,
65 | METHOD_ARCHIVE_LIST,
66 | METHOD_DOWNLOAD_ARCHIVE,
67 | METHOD_VOTE_TAG,
68 |
69 | //Added by EHentaiAPI.
70 | METHOD_GET_GALLERY_PAGE_API,
71 | METHOD_GET_GALLERY_PAGE,
72 | }
73 |
74 | public Task Execute(EhRequest request) => Execute
"))
20 | {
21 | throw new EhException("Signing in is required");
22 | }
23 | string[] catArray = new string[10];
24 | int[] countArray = new int[10];
25 |
26 | try
27 | {
28 | var d = Utils.Document.Parse(body);
29 | var ido = d.GetElementByClass("ido");
30 | //noinspection ConstantConditions
31 | var fps = ido.GetElementsByClassName("fp").ToArray();
32 | // Last one is "fp fps"
33 | Debug.Assert(11 == fps.Length);
34 |
35 | for (int i = 0; i < 10; i++)
36 | {
37 | var fp = fps[i];
38 | countArray[i] = ParserUtils.ParseInt(fp.Children[0].Text(), 0);
39 | catArray[i] = ParserUtils.Trim(fp.Children[2].Text());
40 | }
41 | }
42 | catch (Exception e)
43 | {
44 | e.PrintStackTrace();
45 | throw new ParseException("Parse favorites error", body);
46 | }
47 |
48 | GalleryListParser.Result result = GalleryListParser.Parse(settings, body);
49 |
50 | Result re = new Result();
51 | re.catArray = catArray;
52 | re.countArray = countArray;
53 | re.pages = result.pages;
54 | re.nextPage = result.nextPage;
55 | re.galleryInfoList = result.galleryInfoList;
56 |
57 | return re;
58 | }
59 |
60 | public class Result
61 | {
62 | public string[] catArray; // Size 10
63 | public int[] countArray; // Size 10
64 | public int pages;
65 | public int nextPage;
66 | public List galleryInfoList;
67 | }
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/ForumsParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Exceptions;
2 | using EHentaiAPI.Utils;
3 | using EHentaiAPI.Utils.ExtensionMethods;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace EHentaiAPI.Client.Parser
11 | {
12 | public class ForumsParser
13 | {
14 | public static string Parse(string body)
15 | {
16 | try
17 | {
18 | Document d = Document.Parse(body);
19 | var userlinks = d.GetElementById("userlinks");
20 | var child = userlinks.Children[0].Children[0].Children[0];
21 | return child.GetAttributeEx("href");
22 | }
23 | catch (Exception)
24 | {
25 | throw new ParseException("Parse forums error", body);
26 | }
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryApiParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Data;
2 | using EHentaiAPI.Utils.ExtensionMethods;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace EHentaiAPI.Client.Parser
12 | {
13 | public class GalleryApiParser
14 | {
15 |
16 | public static void Parse(Settings settings, string body, List galleryInfoList)
17 | {
18 | var jo = JsonConvert.DeserializeObject(body);
19 | var ja = jo.GetValue("gmetadata") as JArray;
20 |
21 | for (int i = 0, length = ja.Count; i < length; i++)
22 | {
23 | var g = ja[i] as JObject;
24 | long gid = g[("gid")].ToObject();
25 | GalleryInfo gi = GetGalleryInfoByGid(galleryInfoList, gid);
26 | if (gi == null)
27 | continue;
28 | gi.Title = ParserUtils.Trim(g.GetString("title"));
29 | gi.TitleJpn = ParserUtils.Trim(g.GetString("title_jpn"));
30 | gi.Category = EhUtils.GetCategory(g.GetString("category"));
31 | gi.Thumb = EhUtils.HandleThumbUrlResolution(settings,g.GetString("thumb"));
32 | gi.Uploader = g.GetString("uploader");
33 | gi.Posted = ParserUtils.FormatDate(ParserUtils.ParseLong(g.GetString("posted"), 0) * 1000);
34 | gi.Rating = ParserUtils.ParseFloat(g.GetString("rating"), 0.0f);
35 | // tags
36 | var tagJa = g.GetJSONArray("tags");
37 | int tagLength = tagJa.Count;
38 | string[] tags = new string[tagLength];
39 | for (int j = 0; j < tagLength; j++)
40 | {
41 | tags[j] = tagJa.GetString(j.ToString());
42 | }
43 | gi.SimpleTags = tags;
44 | gi.Pages = ParserUtils.ParseInt(g.GetString("filecount"), 0);
45 | gi.GenerateSLang();
46 | }
47 | }
48 |
49 | private static GalleryInfo GetGalleryInfoByGid(List galleryInfoList, long gid)
50 | {
51 | for (int i = 0, size = galleryInfoList.Count; i < size; i++)
52 | {
53 | GalleryInfo gi = galleryInfoList[i];
54 | if (gi.Gid == gid)
55 | {
56 | return gi;
57 | }
58 | }
59 | return null;
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryDetailUrlParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Client.Parser
9 | {
10 | public class GalleryDetailUrlParser
11 | {
12 |
13 | private static readonly Regex URL_STRICT_PATTERN = new Regex(
14 | "https?://(?:" + EhUrl.DOMAIN_EX + "|" + EhUrl.DOMAIN_E + "|" + EhUrl.DOMAIN_LOFI + ")/(?:g|mpv)/(\\d+)/([0-9a-f]{10})");
15 |
16 | private static readonly Regex URL_PATTERN = new Regex(
17 | "(\\d+)/([0-9a-f]{10})(?:[^0-9a-f]|$)");
18 |
19 |
20 | public static Result Parse(string url)
21 | {
22 | return Parse(url, true);
23 | }
24 |
25 |
26 | public static Result Parse(string url, bool strict)
27 | {
28 | if (url == null)
29 | {
30 | return null;
31 | }
32 |
33 | Regex pattern = strict ? URL_STRICT_PATTERN : URL_PATTERN;
34 | var m = pattern.Match(url);
35 | if (m.Success)
36 | {
37 | var result = new Result();
38 | result.gid = ParserUtils.ParseLong(m.Groups[1].Value, -1L);
39 | result.token = m.Groups[2].Value;
40 | if (result.gid < 0)
41 | {
42 | return null;
43 | }
44 | return result;
45 | }
46 | else
47 | {
48 | return null;
49 | }
50 | }
51 |
52 | public class Result
53 | {
54 | public long gid;
55 | public string token;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryListParser.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using EHentaiAPI.Client.Data;
3 | using EHentaiAPI.Client.Exceptions;
4 | using EHentaiAPI.ExtendFunction;
5 | using EHentaiAPI.Utils;
6 | using EHentaiAPI.Utils.ExtensionMethods;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Linq;
10 | using System.Text;
11 | using System.Text.RegularExpressions;
12 | using System.Threading.Tasks;
13 |
14 | namespace EHentaiAPI.Client.Parser
15 | {
16 | public class GalleryListParser
17 | {
18 |
19 | private const string TAG = nameof(GalleryListParser);
20 |
21 | private static readonly Regex PATTERN_RATING = new Regex("\\d+px");
22 | private static readonly Regex PATTERN_THUMB_SIZE = new Regex("height:(\\d+)px;width:(\\d+)px");
23 | private static readonly Regex PATTERN_FAVORITE_SLOT = new Regex("background-color:rgba\\((\\d+),(\\d+),(\\d+),");
24 | private static readonly Regex PATTERN_PAGES = new Regex("(\\d+) page");
25 | private static readonly Regex PATTERN_NEXT_PAGE = new Regex("page=(\\d+)");
26 |
27 | private static readonly string[][] FAVORITE_SLOT_RGB = new string[][]{
28 | new string[]{"0", "0", "0"},
29 | new string[]{"240", "0", "0"},
30 | new string[] { "240", "160", "0" },
31 | new string[] { "208", "208", "0" },
32 | new string[] { "0", "128", "0" },
33 | new string[] { "144", "240", "64" },
34 | new string[] { "64", "176", "240" },
35 | new string[] { "0", "0", "240" },
36 | new string[] { "80", "0", "128" },
37 | new string[] { "224", "128", "224" },
38 | };
39 |
40 | private static int ParsePages(Utils.Document d, string body)
41 | {
42 | try
43 | {
44 | var es = d.GetElementsByClass("ptt").FirstOrDefault().Children[0].Children[0].Children;
45 | return int.Parse(es[es.Length - 2].Text().Trim());
46 | }
47 | catch (Exception)
48 | {
49 | //ExceptionUtils.throwIfFatal(e);
50 | throw new ParseException("Can't parse gallery list pages", body);
51 | }
52 | }
53 |
54 | private static string ParseRating(string ratingStyle)
55 | {
56 | var m = PATTERN_RATING.Match(ratingStyle);
57 | int num1 = int.MinValue;
58 | int num2 = int.MinValue;
59 | int rate = 5;
60 | string re;
61 | if (m.Success)
62 | {
63 | num1 = ParserUtils.ParseInt(m.Groups[0].Value.Replace("px", ""), int.MinValue);
64 | }
65 | if (m.Success)
66 | {
67 | num2 = ParserUtils.ParseInt(m.Groups[0].Value.Replace("px", ""), int.MinValue);
68 | }
69 | if (num1 == int.MinValue || num2 == int.MinValue)
70 | {
71 | return null;
72 | }
73 | rate -= num1 / 16;
74 | if (num2 == 21)
75 | {
76 | rate--;
77 | re = rate.ToString();
78 | re += ".5";
79 | }
80 | else
81 | re = rate.ToString();
82 | return re;
83 | }
84 |
85 | private static int ParseFavoriteSlot(string style)
86 | {
87 | var m = PATTERN_FAVORITE_SLOT.Match(style);
88 | if (m.Success)
89 | {
90 | string r = m.Groups[1].Value;
91 | string g = m.Groups[2].Value;
92 | string b = m.Groups[3].Value;
93 | int slot = 0;
94 | foreach (string[] rgb in FAVORITE_SLOT_RGB)
95 | {
96 | if (r.Equals(rgb[0]) && g.Equals(rgb[1]) && b.Equals(rgb[2]))
97 | {
98 | return slot;
99 | }
100 | slot++;
101 | }
102 | }
103 | return -2;
104 | }
105 |
106 | private static GalleryInfo ParseGalleryInfo(Settings settings, IElement e)
107 | {
108 | GalleryInfo gi = new GalleryInfo();
109 |
110 | // Title, gid, token (required), tags
111 | var glname = e.GetElementsByClassName("glname").FirstOrDefault();
112 | if (glname != null)
113 | {
114 | var a = glname.GetElementsByTagName("a").FirstOrDefault();
115 | if (a == null)
116 | {
117 | var parent = glname.ParentElement;
118 | if (parent != null && "a".Equals(parent.TagName, StringComparison.InvariantCultureIgnoreCase))
119 | {
120 | a = parent;
121 | }
122 | }
123 | if (a != null)
124 | {
125 | GalleryDetailUrlParser.Result result = GalleryDetailUrlParser.Parse(a.GetAttributeEx("href"));
126 | if (result != null)
127 | {
128 | gi.Gid = result.gid;
129 | gi.Token = result.token;
130 | }
131 | }
132 |
133 | var Children = glname;
134 | var children = glname.Children;
135 | while (children.Length != 0)
136 | {
137 | Children = children[0];
138 | children = Children.Children;
139 | }
140 | gi.Title = Children.Text().Trim();
141 |
142 | var tbody = glname.GetElementsByTagName("tbody").FirstOrDefault();
143 | if (tbody != null)
144 | {
145 | List tags = new();
146 | GalleryTagGroup[] groups = GalleryDetailParser.ParseTagGroups(tbody.Children);
147 | foreach (GalleryTagGroup Groups in groups)
148 | {
149 | for (int j = 0; j < Groups.Size; j++)
150 | {
151 | tags.Add(Groups.TagGroupName + ":" + Groups.GetTagAt(j));
152 | }
153 | }
154 | gi.SimpleTags = tags.ToArray();
155 | }
156 | }
157 | if (gi.Title == null)
158 | {
159 | return null;
160 | }
161 |
162 | // Category
163 | gi.Category = EhUtils.UNKNOWN;
164 | var ce = e.GetElementByIdRecursive("cn");
165 | if (ce == null)
166 | {
167 | ce = e.GetElementsByClassName("cs").FirstOrDefault();
168 | }
169 | if (ce != null)
170 | {
171 | gi.Category = EhUtils.GetCategory(ce.Text());
172 | }
173 |
174 | // Thumb
175 | var glthumb = e.GetElementByIdRecursive("glthumb");
176 | if (glthumb != null)
177 | {
178 | var img = glthumb.QuerySelectorAll("div:nth-child(1)>img").FirstOrDefault();
179 | if (img != null)
180 | {
181 | // Thumb size
182 | var m = PATTERN_THUMB_SIZE.Match(img.GetAttributeEx("style"));
183 | if (m.Success)
184 | {
185 | gi.ThumbWidth = ParserUtils.ParseInt(m.Groups[2].Value, 0);
186 | gi.ThumbHeight = ParserUtils.ParseInt(m.Groups[1].Value, 0);
187 | }
188 | else
189 | {
190 | Log.W(TAG, "Can't parse gallery info thumb size");
191 | gi.ThumbWidth = 0;
192 | gi.ThumbHeight = 0;
193 | }
194 | // Thumb url
195 | var url = img.GetAttributeEx("data-src");
196 | if (string.IsNullOrWhiteSpace(url))
197 | {
198 | url = img.GetAttributeEx("src");
199 | }
200 | if (string.IsNullOrWhiteSpace(url))
201 | {
202 | url = null;
203 | }
204 | gi.Thumb = EhUtils.HandleThumbUrlResolution(settings, url);
205 | }
206 |
207 | // Pages
208 | var div = glthumb.QuerySelectorAll("div:nth-child(2)>div:nth-child(2)>div:nth-child(2)").FirstOrDefault();
209 | if (div != null)
210 | {
211 | var Match = PATTERN_PAGES.Match(div.Text());
212 | if (Match.Success)
213 | {
214 | gi.Pages = ParserUtils.ParseInt(Match.Groups[1].Value, 0);
215 | }
216 | }
217 | }
218 |
219 | IElement gl;
220 | // Try extended and thumbnail version
221 | if (gi.Thumb == null)
222 | {
223 | gl = e.GetElementsByClassName("gl1e").FirstOrDefault();
224 | if (gl == null)
225 | {
226 | gl = e.GetElementsByClassName("gl3t").FirstOrDefault();
227 | }
228 | if (gl != null)
229 | {
230 | var img = gl.GetElementsByTagName("img").FirstOrDefault();
231 | if (img != null)
232 | {
233 | // Thumb size
234 | var m = PATTERN_THUMB_SIZE.Match(img.GetAttributeEx("style"));
235 | if (m.Success)
236 | {
237 | gi.ThumbWidth = ParserUtils.ParseInt(m.Groups[2].Value, 0);
238 | gi.ThumbHeight = ParserUtils.ParseInt(m.Groups[1].Value, 0);
239 | }
240 | else
241 | {
242 | Log.W(TAG, "Can't parse gallery info thumb size");
243 | gi.ThumbWidth = 0;
244 | gi.ThumbHeight = 0;
245 | }
246 | gi.Thumb = EhUtils.HandleThumbUrlResolution(settings, img.GetAttributeEx("src"));
247 | }
248 | }
249 | }
250 |
251 | // Posted
252 | gi.FavoriteSlot = -2;
253 | var postedId = "posted_" + gi.Gid;
254 | var posted = e.GetElementByIdRecursive(postedId);
255 | if (posted != null)
256 | {
257 | gi.Posted = posted.Text().Trim();
258 | gi.FavoriteSlot = ParseFavoriteSlot(posted.GetAttributeEx("style"));
259 | }
260 |
261 | // Rating
262 | var ir = e.GetElementsByClassName("ir").FirstOrDefault();
263 | if (ir != null)
264 | {
265 | gi.Rating = ParserUtils.ParseFloat(ParseRating(ir.GetAttributeEx("style")), -1.0f);
266 | // TODO The gallery may be rated even if it doesn't has one of these classes
267 | gi.Rated = ir.ClassList.Contains("irr") || ir.ClassList.Contains("irg") || ir.ClassList.Contains("irb");
268 | }
269 |
270 | // Uploader and pages
271 | gl = e.GetElementsByClassName("glhide").FirstOrDefault();
272 | int uploaderIndex = 0;
273 | int pagesIndex = 1;
274 | if (gl == null)
275 | {
276 | // For extended
277 | gl = e.GetElementsByClassName("gl3e").FirstOrDefault();
278 | uploaderIndex = 3;
279 | pagesIndex = 4;
280 | }
281 | if (gl != null)
282 | {
283 | var children = gl.Children;
284 | if (children.Length > uploaderIndex)
285 | {
286 | var a = children[uploaderIndex].Children.FirstOrDefault();
287 | if (a != null)
288 | {
289 | gi.Uploader = a.Text().Trim();
290 | }
291 | }
292 | if (children.Length > pagesIndex)
293 | {
294 | var Match = PATTERN_PAGES.Match(children[pagesIndex].Text());
295 | if (Match.Success)
296 | {
297 | gi.Pages = ParserUtils.ParseInt(Match.Groups[1].Value, 0);
298 | }
299 | }
300 | }
301 | // For thumbnail
302 | var gl5t = e.GetElementsByClassName("gl5t").FirstOrDefault();
303 | if (gl5t != null)
304 | {
305 | var div = gl5t.QuerySelectorAll("div:nth-child(2)>div:nth-child(2)").FirstOrDefault();
306 | if (div != null)
307 | {
308 | var Match = PATTERN_PAGES.Match(div.Text());
309 | if (Match.Success)
310 | {
311 | gi.Pages = ParserUtils.ParseInt(Match.Groups[1].Value, 0);
312 | }
313 | }
314 | }
315 |
316 | gi.GenerateSLang();
317 |
318 | return gi;
319 | }
320 |
321 | public static Result Parse(Settings settings, string body)
322 | {
323 | var result = new Result();
324 | var d = Utils.Document.Parse(body);
325 |
326 | try
327 | {
328 | var ptt = d.GetElementsByClass("ptt").FirstOrDefault();
329 | var es = ptt.Children[0].Children[0].Children;
330 | result.pages = int.Parse(es[es.Length - 2].Text().Trim());
331 |
332 | var e = es[es.Length - 1];
333 | if (e != null)
334 | {
335 | e = e.Children.FirstOrDefault();
336 | if (e != null)
337 | {
338 | string href = e.GetAttributeEx("href");
339 | var Match = PATTERN_NEXT_PAGE.Match(href);
340 | if (Match.Success)
341 | {
342 | result.nextPage = ParserUtils.ParseInt(Match.Groups[1].Value, 0);
343 | }
344 | }
345 | }
346 | }
347 | catch (Exception)
348 | {
349 | //ExceptionUtils.throwIfFatal(e);
350 | result.noWatchedTags = body.Contains("You do not have any watched tags");
351 | if (body.Contains("No hits found
"))
352 | {
353 | result.pages = 0;
354 | //noinspection unchecked
355 | result.galleryInfoList = new();
356 | return result;
357 | }
358 | else if (d.GetElementsByClass("ptt").Length == 0)
359 | {
360 | result.pages = 1;
361 | }
362 | else
363 | {
364 | result.pages = int.MaxValue;
365 | }
366 | }
367 |
368 | try
369 | {
370 | var itg = d.GetElementsByClass("itg").FirstOrDefault();
371 | IHtmlCollection es;
372 | if ("table".Equals(itg.TagName, StringComparison.InvariantCultureIgnoreCase))
373 | {
374 | es = itg.Children[0].Children;
375 | }
376 | else
377 | {
378 | es = itg.Children;
379 | }
380 | List list = new(es.Length);
381 | // First one is table header, skip it
382 | for (int i = 0; i < es.Length; i++)
383 | {
384 | GalleryInfo gi = ParseGalleryInfo(settings, es[i]);
385 | if (null != gi)
386 | {
387 | list.Add(gi);
388 | }
389 | }
390 | if (list.Count == 0)
391 | {
392 | throw new ParseException("No gallery", body);
393 | }
394 | result.galleryInfoList = list;
395 | }
396 | catch (Exception e)
397 | {
398 | //ExceptionUtils.throwIfFatal(e);
399 | e.PrintStackTrace();
400 | throw new ParseException("Can't parse gallery list", body);
401 | }
402 |
403 | return result;
404 | }
405 |
406 | public class Result
407 | {
408 | public int pages;
409 | public int nextPage;
410 | public bool noWatchedTags;
411 | public List galleryInfoList;
412 | }
413 | }
414 | }
415 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryMultiPageViewerPTokenParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Exceptions;
2 | using EHentaiAPI.Utils.ExtensionMethods;
3 | using Newtonsoft.Json.Linq;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace EHentaiAPI.Client.Parser
11 | {
12 | public class GalleryMultiPageViewerPTokenParser
13 | {
14 | public static List Parse(string body)
15 | {
16 | var list = new List();
17 | var pos = body.IndexOf("var imagelist = ") + 16;
18 | var ei = body.IndexOf(";", body.IndexOf("var imagelist = "));
19 | var imagelist = body.Substring(pos, ei - pos);
20 | try
21 | {
22 | var ja = new JArray(imagelist);
23 | for (int i = 0; i < ja.Count; i++)
24 | {
25 | var jo = ja[i];
26 | list.Add(jo.GetString("k"));
27 | }
28 | }
29 | catch
30 | {
31 | //ExceptionUtils.throwIfFatal(e);
32 | throw new EhException(body);
33 | }
34 | return list;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryNotAvailableParser.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using EHentaiAPI.Utils;
3 | using EHentaiAPI.Utils.ExtensionMethods;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace EHentaiAPI.Client.Parser
11 | {
12 | public class GalleryNotAvailableParser
13 | {
14 | public static string Parse(string body)
15 | {
16 | string error = null;
17 | try
18 | {
19 | var document = Utils.Document.Parse(body);
20 | var d = document.GetElementByClass("d");
21 | //noinspection ConstantConditions
22 | error = d.Children[0].Html();
23 | error = error.Replace("
", "\n");
24 | }
25 | catch (Exception e)
26 | {
27 | //ExceptionUtils.throwIfFatal(e);
28 | e.PrintStackTrace();
29 | }
30 | return error;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryPageApiParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Exceptions;
2 | using EHentaiAPI.Utils.ExtensionMethods;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Text.RegularExpressions;
10 | using System.Threading.Tasks;
11 |
12 | namespace EHentaiAPI.Client.Parser
13 | {
14 | public class GalleryPageApiParser
15 | {
16 | private static readonly Regex PATTERN_IMAGE_URL = new Regex("
]*src=\"([^\"]+)\" style");
17 | private static readonly Regex PATTERN_SKIP_HATH_KEY = new Regex("onclick=\"return nl\\('([^\\)]+)'\\)");
18 | private static readonly Regex PATTERN_ORIGIN_IMAGE_URL = new Regex("");
19 |
20 |
21 | public static Result Parse(string body)
22 | {
23 | try
24 | {
25 | Match m;
26 | Result result = new Result();
27 |
28 | var jo = JsonConvert.DeserializeObject(body);
29 | if (jo.ContainsKey("error"))
30 | {
31 | throw new ParseException(jo.GetString("error"), body);
32 | }
33 |
34 | string i3 = jo.GetString("i3");
35 | m = PATTERN_IMAGE_URL.Match(i3);
36 | if (m.Success)
37 | {
38 | result.imageUrl = ParserUtils.UnescapeXml(ParserUtils.Trim(m.Groups[1].Value));
39 | }
40 | string i6 = jo.GetString("i6");
41 | m = PATTERN_SKIP_HATH_KEY.Match(i6);
42 | if (m.Success)
43 | {
44 | result.skipHathKey = ParserUtils.UnescapeXml(ParserUtils.Trim(m.Groups[1].Value));
45 | }
46 | string i7 = jo.GetString("i7");
47 | m = PATTERN_ORIGIN_IMAGE_URL.Match(i7);
48 | if (m.Success)
49 | {
50 | result.originImageUrl = ParserUtils.UnescapeXml(m.Groups[1].Value) + "fullimg.php" + ParserUtils.UnescapeXml(m.Groups[2].Value);
51 | }
52 |
53 | if (!string.IsNullOrWhiteSpace(result.imageUrl))
54 | {
55 | return result;
56 | }
57 | else
58 | {
59 | throw new ParseException("Parse image url and skip hath key error", body);
60 | }
61 | }
62 | catch (Exception e)
63 | {
64 | throw new ParseException("Can't parse json", body, e);
65 | }
66 | }
67 |
68 | public class Result
69 | {
70 | public string imageUrl;
71 | public string skipHathKey;
72 | public string originImageUrl;
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryPageParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Exceptions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 |
9 | namespace EHentaiAPI.Client.Parser
10 | {
11 | public class GalleryPageParser
12 | {
13 |
14 | private readonly static Regex PATTERN_IMAGE_URL = new Regex("
]*src=\"([^\"]+)\" style");
15 | private readonly static Regex PATTERN_SKIP_HATH_KEY = new Regex("onclick=\"return nl\\('([^\\)]+)'\\)");
16 | private readonly static Regex PATTERN_ORIGIN_IMAGE_URL = new Regex("");
17 | // TODO Not sure about the size of show keys
18 | private readonly static Regex PATTERN_SHOW_KEY = new Regex("var showkey=\"([0-9a-z]+)\";");
19 |
20 |
21 | public static Result Parse(string body)
22 | {
23 | Match m;
24 | Result result = new Result();
25 | m = PATTERN_IMAGE_URL.Match(body);
26 | if (m.Success)
27 | {
28 | result.imageUrl = ParserUtils.UnescapeXml(ParserUtils.Trim(m.Groups[1].Value));
29 | }
30 | m = PATTERN_SKIP_HATH_KEY.Match(body);
31 | if (m.Success)
32 | {
33 | result.skipHathKey = ParserUtils.UnescapeXml(ParserUtils.Trim(m.Groups[1].Value));
34 | }
35 | m = PATTERN_ORIGIN_IMAGE_URL.Match(body);
36 | if (m.Success)
37 | {
38 | result.originImageUrl = ParserUtils.UnescapeXml(m.Groups[1].Value) + "fullimg.php" + ParserUtils.UnescapeXml(m.Groups[2].Value);
39 | }
40 | m = PATTERN_SHOW_KEY.Match(body);
41 | if (m.Success)
42 | {
43 | result.showKey = m.Groups[1].Value;
44 | }
45 |
46 | if (!string.IsNullOrWhiteSpace(result.imageUrl) && !string.IsNullOrWhiteSpace(result.showKey))
47 | {
48 | return result;
49 | }
50 | else
51 | {
52 | throw new ParseException("Parse image url and show error", body);
53 | }
54 | }
55 |
56 | public class Result
57 | {
58 | public string imageUrl;
59 | public string skipHathKey;
60 | public string originImageUrl;
61 | public string showKey;
62 | }
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryPageUrlParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Client.Parser
9 | {
10 | public class GalleryPageUrlParser
11 | {
12 |
13 | private static readonly Regex URL_STRICT_PATTERN = new Regex(
14 | "https?://(?:" + EhUrl.DOMAIN_EX + "|" + EhUrl.DOMAIN_E + "|" + EhUrl.DOMAIN_LOFI + ")/s/([0-9a-f]{10})/(\\d+)-(\\d+)");
15 |
16 | private static readonly Regex URL_PATTERN = new Regex(
17 | "([0-9a-f]{10})/(\\d+)-(\\d+)");
18 |
19 | public static Result Parse(string url)
20 | {
21 | return Parse(url, true);
22 | }
23 |
24 | public static Result Parse(string url, bool strict)
25 | {
26 | if (url == null)
27 | {
28 | return null;
29 | }
30 |
31 | var pattern = strict ? URL_STRICT_PATTERN : URL_PATTERN;
32 | Match m = pattern.Match(url);
33 | if (m.Success)
34 | {
35 | Result result = new Result();
36 | result.gid = ParserUtils.ParseLong(m.Groups[2].Value, -1L);
37 | result.pToken = m.Groups[1].Value;
38 | result.page = ParserUtils.ParseInt(m.Groups[3].Value, 0) - 1;
39 | if (result.gid < 0 || result.page < 0)
40 | {
41 | return null;
42 | }
43 | return result;
44 | }
45 | else
46 | {
47 | return null;
48 | }
49 | }
50 |
51 | public class Result
52 | {
53 | public long gid;
54 | public string pToken;
55 | public int page;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/GalleryTokenApiParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Exceptions;
2 | using EHentaiAPI.Utils.ExtensionMethods;
3 | using Newtonsoft.Json;
4 | using Newtonsoft.Json.Linq;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Linq;
8 | using System.Text;
9 | using System.Threading.Tasks;
10 |
11 | namespace EHentaiAPI.Client.Parser
12 | {
13 | public class GalleryTokenApiParser
14 | {
15 |
16 | /**
17 | * {
18 | * "tokenlist": [
19 | * {
20 | * "gid":618395,
21 | * "token":"0439fa3666"
22 | * }
23 | * ],
24 | * "error":"maomao is moe~"
25 | * }
26 | */
27 | public static string Parse(string body)
28 | {
29 | var jo = JsonConvert.DeserializeObject(body);
30 | var ja = jo.GetJSONArray("tokenlist");
31 |
32 | try
33 | {
34 | return ja[0].GetString("token");
35 | }
36 | catch (Exception)
37 | {
38 | //ExceptionUtils.throwIfFatal(e);
39 | throw new EhException(jo.GetString("error"));
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/ParserUtils.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Client.Parser
8 | {
9 | internal class ParserUtils
10 | {
11 | private static readonly string[] ESCAPE_CHARATER_LIST = {
12 | "&",
13 | "<",
14 | ">",
15 | """,
16 | "'",
17 | "×",
18 | " "
19 | };
20 |
21 | private static readonly string[] UNESCAPE_CHARATER_LIST = {
22 | "&",
23 | "<",
24 | ">",
25 | "\"",
26 | "'",
27 | "×",
28 | " "
29 | };
30 |
31 | public static string UnescapeXml(string str)
32 | {
33 | for (int i = 0; i < ESCAPE_CHARATER_LIST.Length; i++)
34 | {
35 | str = str.Replace(ESCAPE_CHARATER_LIST[i], UNESCAPE_CHARATER_LIST[i]);
36 | }
37 | return str;
38 | }
39 |
40 | public static string Trim(string str)
41 | {
42 | // Avoid null
43 | if (str == null)
44 | {
45 | str = "";
46 | }
47 | return UnescapeXml(str).Trim();
48 | }
49 |
50 | public static int ParseInt(string p, int v)
51 | {
52 | return int.TryParse(p.Trim(), out var d) ? d : v;
53 | }
54 |
55 | public static string FormatDate(long v)
56 | {
57 | //todo
58 | return "miaomiao";
59 | }
60 |
61 | public static long ParseLong(string p, long v)
62 | {
63 | return long.TryParse(p.Trim(), out var d) ? d : v;
64 | }
65 |
66 | public static float ParseFloat(string p, float v)
67 | {
68 | return float.TryParse(p.Trim(), out var d) ? d : v;
69 | }
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/ProfileParser.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using EHentaiAPI.Client.Exceptions;
3 | using EHentaiAPI.ExtendFunction;
4 | using EHentaiAPI.Utils;
5 | using EHentaiAPI.Utils.ExtensionMethods;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace EHentaiAPI.Client.Parser
13 | {
14 | public class ProfileParser
15 | {
16 | private const string TAG = nameof(ProfileParser);
17 |
18 | public static Result Parse(string body)
19 | {
20 | try
21 | {
22 | var result = new Result();
23 | var d = Utils.Document.Parse(body);
24 | var profilename = d.GetElementById("profilename");
25 | result.displayName = profilename.Children[0].Text();
26 | try
27 | {
28 | result.avatar = profilename.NextElementSibling.NextElementSibling.Children[0].GetAttributeEx("src");
29 | if (string.IsNullOrWhiteSpace(result.avatar))
30 | {
31 | result.avatar = null;
32 | }
33 | else if (!result.avatar.StartsWith("http"))
34 | {
35 | result.avatar = EhUrl.URL_FORUMS + result.avatar;
36 | }
37 | }
38 | catch (Exception)
39 | {
40 | //ExceptionUtils.throwIfFatal(e);
41 | Log.I(TAG, "No avatar");
42 | }
43 | return result;
44 | }
45 | catch (Exception)
46 | {
47 | //ExceptionUtils.throwIfFatal(e);
48 | throw new ParseException("Parse forums error", body);
49 | }
50 | }
51 |
52 | public class Result
53 | {
54 | public string displayName;
55 | public string avatar;
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/RateGalleryParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Exceptions;
2 | using Newtonsoft.Json;
3 | using Newtonsoft.Json.Linq;
4 | using System;
5 | using System.Collections.Generic;
6 | using System.Linq;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace EHentaiAPI.Client.Parser
11 | {
12 | public class RateGalleryParser
13 | {
14 |
15 | public static Result Parse(string body)
16 | {
17 | try
18 | {
19 | var jsonObject = JsonConvert.DeserializeObject(body);
20 | Result result = new Result();
21 | result.rating = jsonObject.Value("rating_avg");
22 | result.ratingCount = jsonObject.Value("rating_cnt");
23 | return result;
24 | }
25 | catch (Exception e)
26 | {
27 | throw new ParseException("Can't parse rate gallery", body, e);
28 | }
29 | }
30 |
31 | public class Result
32 | {
33 | public float rating;
34 | public int ratingCount;
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/SignInParser.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client.Exceptions;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Text.RegularExpressions;
7 | using System.Threading.Tasks;
8 |
9 | namespace EHentaiAPI.Client.Parser
10 | {
11 | public class SignInParser
12 | {
13 |
14 | private static readonly Regex NAME_PATTERN = new Regex("You are now logged in as: (.+?)<");
15 | private static readonly Regex ERROR_PATTERN = new Regex(
16 | "(?:
The error returned was:
\\s*(.+?)
)"
17 | + "|(?:(.+?))");
18 |
19 | public static string Parse(string body)
20 | {
21 | var m = NAME_PATTERN.Match(body);
22 | if (m.Success)
23 | {
24 | return m.Groups[1].Value;
25 | }
26 | else
27 | {
28 | m = ERROR_PATTERN.Match(body);
29 | if (m.Success)
30 | {
31 | throw new EhException(m.Groups[1].Value ?? m.Groups[2].Value);
32 | }
33 | else
34 | {
35 | throw new ParseException("Can't parse sign in", body);
36 | }
37 | }
38 | }
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/TorrentParser.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Text.RegularExpressions;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Client.Parser
9 | {
10 | public class TorrentParser
11 | {
12 | private static readonly Regex PATTERN_TORRENT = new Regex(" ([^<]+) | ");
13 |
14 | public static KeyValuePair[] Parse(string body)
15 | {
16 | List> torrentList = new();
17 | var m = PATTERN_TORRENT.Match(body);
18 | while (m.Success)
19 | {
20 | string url = ParserUtils.Trim(m.Groups[1].Value);
21 | string name = ParserUtils.Trim(m.Groups[2].Value);
22 | var item = KeyValuePair.Create(url, name);
23 | torrentList.Add(item);
24 | m = m.NextMatch();
25 | }
26 | return torrentList.ToArray();
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/VoteCommentParser.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace EHentaiAPI.Client.Parser
10 | {
11 | public class VoteCommentParser
12 | {
13 | // {"comment_id":1253922,"comment_score":-19,"comment_vote":0}
14 | public static Result Parse(string body, int vote)
15 | {
16 | var result = new Result();
17 | var jo = JsonConvert.DeserializeObject(body);
18 | result.id = jo.Value("comment_id");
19 | result.score = jo.Value("comment_score");
20 | result.vote = jo.Value("comment_vote");
21 | result.expectVote = vote;
22 | return result;
23 | }
24 |
25 | public class Result
26 | {
27 | public long id;
28 | public int score;
29 | public int vote;
30 | public int expectVote;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/EHentaiAPI/Client/Parser/VoteTagParser.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json;
2 | using Newtonsoft.Json.Linq;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace EHentaiAPI.Client.Parser
10 | {
11 | public class VoteTagParser
12 | {
13 | // {"error":"The tag \"neko\" is not allowed. Use character:neko or artist:neko"}
14 | public static Result Parse(string body)
15 | {
16 | var result = new Result();
17 | var jo = JsonConvert.DeserializeObject(body);
18 | if (jo.ContainsKey("error")) result.error = jo.Value("error");
19 | return result;
20 | }
21 |
22 | public class Result
23 | {
24 | public string error;
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/EHentaiAPI/EHentaiAPI.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net5.0
5 | true
6 | true
7 | 0.6.8
8 | MikiraSora
9 |
10 | .Net porting implementation of EhViewer
11 | https://github.com/MikiraSora/EHentaiAPI
12 | https://github.com/MikiraSora/EHentaiAPI
13 | EHentai
14 | LICENSE
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 | True
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/EHentaiAPI/ExtendFunction/ILog.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.ExtendFunction
8 | {
9 | public interface ILog
10 | {
11 | void D(string tag, string msg, Exception e);
12 | void I(string tag, string msg);
13 | void W(string tag, string msg);
14 | void E(string tag, string msg);
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/EHentaiAPI/ExtendFunction/IRequest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Net.Http;
6 | using System.Text;
7 | using System.Threading.Tasks;
8 |
9 | namespace EHentaiAPI.ExtendFunction
10 | {
11 | public interface IRequest
12 | {
13 | public WebHeaderCollection Headers { get; set; }
14 | public CookieContainer Cookies { get; set; }
15 | public string Method { get; set; }
16 | public HttpContent Content { get; set; }
17 |
18 | public Task SendAsync();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/EHentaiAPI/ExtendFunction/IResponse.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Net;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.ExtendFunction
9 | {
10 | public interface IResponse
11 | {
12 | public int StatusCode { get; }
13 | public WebHeaderCollection Headers { get; }
14 |
15 | public Task GetResponseContentAsStringAsync();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/EHentaiAPI/ExtendFunction/Log.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.ExtendFunction
8 | {
9 | public static class Log
10 | {
11 | public static ILog LogImplement { get; set; } = new DefaultLogImpl();
12 |
13 | internal static void D(string tag, string msg, Exception e = default) => LogImplement?.D(tag, msg, e);
14 | internal static void I(string tag, string msg) => LogImplement?.I(tag, msg);
15 | internal static void E(string tag, string msg) => LogImplement?.E(tag, msg);
16 | internal static void W(string tag, string msg) => LogImplement?.W(tag, msg);
17 | }
18 |
19 | internal class DefaultLogImpl : ILog
20 | {
21 | private readonly StringBuilder sb = new StringBuilder();
22 |
23 | private void Output(string prefix, string tag, string msg, Exception e = default)
24 | {
25 | sb.Clear();
26 | sb.Append('[').Append(prefix).Append(']').Append(tag).Append(':').Append(msg);
27 | if (e != null)
28 | sb.AppendLine().Append(e.StackTrace);
29 | Console.WriteLine(sb.ToString());
30 | }
31 |
32 | public void D(string tag, string msg, Exception e) => Output("DEBUG", tag, msg, e);
33 |
34 | public void E(string tag, string msg) => Output("ERROR", tag, msg);
35 |
36 | public void I(string tag, string msg) => Output("INFO", tag, msg);
37 |
38 | public void W(string tag, string msg) => Output("WARN", tag, msg);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/EHentaiAPI/ExtendFunction/Request.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Net;
6 | using System.Net.Http;
7 | using System.Text;
8 | using System.Threading.Tasks;
9 |
10 | namespace EHentaiAPI.ExtendFunction
11 | {
12 | public static class Request
13 | {
14 | public static Func RequestFactory { get; set; } = (url) => new DefaultRequestImpl(url);
15 |
16 | internal static IRequest Create(string url)
17 | {
18 | return RequestFactory?.Invoke(url);
19 | }
20 | }
21 |
22 | internal class DefaultRequestImpl : IRequest
23 | {
24 | private HttpWebRequest request;
25 |
26 | public WebHeaderCollection Headers
27 | {
28 | get
29 | {
30 | return request.Headers;
31 | }
32 | set
33 | {
34 | request.Headers = value;
35 | }
36 | }
37 |
38 | public CookieContainer Cookies
39 | {
40 | get
41 | {
42 | return request.CookieContainer;
43 | }
44 |
45 | set
46 | {
47 | request.CookieContainer = value;
48 | }
49 | }
50 |
51 | public string Method
52 | {
53 | get
54 | {
55 |
56 | return request.Method;
57 | }
58 | set
59 | {
60 | request.Method = value?.ToUpper();
61 | }
62 | }
63 |
64 | public HttpContent Content { get; set; }
65 |
66 | public DefaultRequestImpl(string url)
67 | {
68 | request = WebRequest.Create(url) as HttpWebRequest;
69 | }
70 |
71 | public async Task SendAsync()
72 | {
73 | if (Content != null)
74 | {
75 | await Content.CopyToAsync(await request.GetRequestStreamAsync());
76 | request.ContentType = Content.Headers.ContentType.ToString();
77 | request.ContentLength = Content.Headers.ContentLength ?? 0;
78 | }
79 |
80 | var response = await request.GetResponseAsync();
81 | return new DefaultResponseImpl(response as HttpWebResponse);
82 | }
83 | }
84 |
85 |
86 | public class DefaultResponseImpl : IResponse
87 | {
88 | private HttpWebResponse response;
89 |
90 | public DefaultResponseImpl(HttpWebResponse response)
91 | {
92 | this.response = response;
93 | }
94 |
95 | public int StatusCode => (int)response.StatusCode;
96 |
97 | public WebHeaderCollection Headers => response.Headers;
98 |
99 | public async Task GetResponseContentAsStringAsync()
100 | {
101 | using var reader = new StreamReader(response.GetResponseStream());
102 | var content = await reader.ReadToEndAsync();
103 | return content;
104 | }
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/EHentaiAPI/Settings.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client;
2 | using EHentaiAPI.Client.Data;
3 | using EHentaiAPI.ExtendFunction;
4 | using EHentaiAPI.Utils;
5 | using System;
6 | using System.Collections.Generic;
7 | using System.Diagnostics;
8 | using System.Linq;
9 | using System.Text;
10 | using System.Threading.Tasks;
11 |
12 | namespace EHentaiAPI
13 | {
14 | public class Settings
15 | {
16 |
17 | /********************
18 | ****** Eh
19 | ********************/
20 |
21 | public const string KEY_THEME = "theme";
22 | public const string KEY_BLACK_DARK_THEME = "black_dark_theme";
23 | public const int THEME_LIGHT = 1;
24 | public const int THEME_SYSTEM = -1;
25 | public const int THEME_BLACK = 2;
26 | public const string KEY_APPLY_NAV_BAR_THEME_COLOR = "apply_nav_bar_theme_color";
27 | public const string KEY_GALLERY_SITE = "gallery_site";
28 | public const string KEY_LIST_MODE = "list_mode";
29 | public const string KEY_DETAIL_SIZE = "detail_size";
30 | public const string KEY_THUMB_SIZE = "thumb_size";
31 | public const string KEY_THUMB_RESOLUTION = "thumb_resolution";
32 | public const string KEY_SHOW_TAG_TRANSLATIONS = "show_tag_translations";
33 | public const string KEY_DEFAULT_CATEGORIES = "default_categories";
34 | public const int DEFAULT_DEFAULT_CATEGORIES = EhUtils.ALL_CATEGORY;
35 | public const string KEY_EXCLUDED_TAG_NAMESPACES = "excluded_tag_namespaces";
36 | public const string KEY_EXCLUDED_LANGUAGES = "excluded_languages";
37 | /********************
38 | ****** Privacy and Security
39 | ********************/
40 | public const string KEY_SEC_SECURITY = "enable_secure";
41 | public const bool VALUE_SEC_SECURITY = false;
42 | /********************
43 | ****** Download
44 | ********************/
45 | public const string KEY_DOWNLOAD_SAVE_SCHEME = "image_scheme";
46 | public const string KEY_DOWNLOAD_SAVE_AUTHORITY = "image_authority";
47 | public const string KEY_DOWNLOAD_SAVE_PATH = "image_path";
48 | public const string KEY_DOWNLOAD_SAVE_QUERY = "image_query";
49 | public const string KEY_DOWNLOAD_SAVE_FRAGMENT = "image_fragment";
50 | public const string KEY_MEDIA_SCAN = "media_scan";
51 | public const string KEY_IMAGE_RESOLUTION = "image_size";
52 | public const string DEFAULT_IMAGE_RESOLUTION = EhConfig.IMAGE_SIZE_AUTO;
53 | public const int INVALID_DEFAULT_FAV_SLOT = -2;
54 | public const string KEY_ENABLE_ANALYTICS = "enable_analytics";
55 | /********************
56 | ****** Advanced
57 | ********************/
58 | public const string KEY_SAVE_PARSE_ERROR_BODY = "save_parse_error_body";
59 | public const string KEY_SECURITY = "security";
60 | public const string DEFAULT_SECURITY = "";
61 | public const string KEY_ENABLE_FINGERPRINT = "enable_fingerprint";
62 | public const string KEY_READ_CACHE_SIZE = "read_cache_size";
63 | public const int DEFAULT_READ_CACHE_SIZE = 320;
64 | public const string KEY_BUILT_IN_HOSTS = "built_in_hosts_2";
65 | public const string KEY_DOMAIN_FRONTING = "domain_fronting";
66 | public const string KEY_APP_LANGUAGE = "app_language";
67 | private const string TAG = nameof(Settings);
68 | private const string KEY_VERSION_CODE = "version_code";
69 | private const int DEFAULT_VERSION_CODE = 0;
70 | private const string KEY_DISPLAY_NAME = "display_name";
71 | private const string DEFAULT_DISPLAY_NAME = null;
72 | private const string KEY_AVATAR = "avatar";
73 | private const string DEFAULT_AVATAR = null;
74 | private const string KEY_SHOW_WARNING = "show_warning";
75 | private const bool DEFAULT_SHOW_WARNING = true;
76 | private const string KEY_REMOVE_IMAGE_FILES = "include_pic";
77 | private const bool DEFAULT_REMOVE_IMAGE_FILES = true;
78 | private const string KEY_NEED_SIGN_IN = "need_sign_in";
79 | private const bool DEFAULT_NEED_SIGN_IN = true;
80 | private const string KEY_SELECT_SITE = "select_site";
81 | private const bool DEFAULT_SELECT_SITE = true;
82 | private const string KEY_QUICK_SEARCH_TIP = "quick_search_tip";
83 | private const bool DEFAULT_QUICK_SEARCH_TIP = true;
84 | private const int DEFAULT_THEME = THEME_SYSTEM;
85 | private const bool DEFAULT_APPLY_NAV_BAR_THEME_COLOR = true;
86 | private const int DEFAULT_GALLERY_SITE = 1;
87 | private const string KEY_LAUNCH_PAGE = "launch_page";
88 | private const int DEFAULT_LAUNCH_PAGE = 0;
89 | private const int DEFAULT_LIST_MODE = 0;
90 | private const int DEFAULT_DETAIL_SIZE = 0;
91 | private const int DEFAULT_THUMB_SIZE = 1;
92 | private const int DEFAULT_THUMB_RESOLUTION = 0;
93 | private const string KEY_FIX_THUMB_URL = "fix_thumb_url";
94 | private const bool DEFAULT_FIX_THUMB_URL = false;
95 | private const string KEY_SHOW_JPN_TITLE = "show_jpn_title";
96 | private const bool DEFAULT_SHOW_JPN_TITLE = false;
97 | private const string KEY_SHOW_GALLERY_PAGES = "show_gallery_pages";
98 | private const bool DEFAULT_SHOW_GALLERY_PAGES = false;
99 | private const bool DEFAULT_SHOW_TAG_TRANSLATIONS = false;
100 | private const int DEFAULT_EXCLUDED_TAG_NAMESPACES = 0;
101 | private const string DEFAULT_EXCLUDED_LANGUAGES = null;
102 | private const string KEY_METERED_NETWORK_WARNING = "cellular_network_warning";
103 | private const bool DEFAULT_METERED_NETWORK_WARNING = false;
104 | private const string KEY_NIGHT_MODE = "night_mode";
105 | private const string DEFAULT_NIGHT_MODE = "-1";
106 | private const string KEY_E_INK_MODE = "e_ink_mode_2";
107 | private const bool DEFAULT_E_INK_MODE = false;
108 | /********************
109 | ****** Read
110 | ********************/
111 | private const string KEY_SCREEN_ROTATION = "screen_rotation";
112 | private const int DEFAULT_SCREEN_ROTATION = 0;
113 | private const string KEY_READING_DIRECTION = "reading_direction";
114 | //private const int DEFAULT_READING_DIRECTION = GalleryView.LAYOUT_RIGHT_TO_LEFT;
115 | private const string KEY_PAGE_SCALING = "page_scaling";
116 | //private const int DEFAULT_PAGE_SCALING = GalleryView.SCALE_FIT;
117 | private const string KEY_START_POSITION = "start_position";
118 | //private const int DEFAULT_START_POSITION = GalleryView.START_POSITION_TOP_RIGHT;
119 | private const string KEY_KEEP_SCREEN_ON = "keep_screen_on";
120 | private const bool DEFAULT_KEEP_SCREEN_ON = false;
121 | private const string KEY_SHOW_CLOCK = "gallery_show_clock";
122 | private const bool DEFAULT_SHOW_CLOCK = true;
123 | private const string KEY_SHOW_PROGRESS = "gallery_show_progress";
124 | private const bool DEFAULT_SHOW_PROGRESS = true;
125 | private const string KEY_SHOW_BATTERY = "gallery_show_battery";
126 | private const bool DEFAULT_SHOW_BATTERY = true;
127 | private const string KEY_SHOW_PAGE_INTERVAL = "gallery_show_page_interval";
128 | private const bool DEFAULT_SHOW_PAGE_INTERVAL = true;
129 | private const string KEY_VOLUME_PAGE = "volume_page";
130 | private const bool DEFAULT_VOLUME_PAGE = false;
131 | private const string KEY_REVERSE_VOLUME_PAGE = "reserve_volume_page";
132 | private const bool DEFAULT_REVERSE_VOLUME_PAGE = false;
133 | private const string KEY_READING_FULLSCREEN = "reading_fullscreen";
134 | private const bool VALUE_READING_FULLSCREEN = true;
135 | private const string KEY_CUSTOM_SCREEN_LIGHTNESS = "custom_screen_lightness";
136 | private const bool DEFAULT_CUSTOM_SCREEN_LIGHTNESS = false;
137 | private const string KEY_SCREEN_LIGHTNESS = "screen_lightness";
138 | private const int DEFAULT_SCREEN_LIGHTNESS = 50;
139 | private const bool DEFAULT_MEDIA_SCAN = false;
140 | private const string KEY_RECENT_DOWNLOAD_LABEL = "recent_download_label";
141 | private const string DEFAULT_RECENT_DOWNLOAD_LABEL = null;
142 | private const string KEY_HAS_DEFAULT_DOWNLOAD_LABEL = "has_default_download_label";
143 | private const bool DEFAULT_HAS_DOWNLOAD_LABEL = false;
144 | private const string KEY_DEFAULT_DOWNLOAD_LABEL = "default_download_label";
145 | private const string DEFAULT_DOWNLOAD_LABEL = null;
146 | private const string KEY_MULTI_THREAD_DOWNLOAD = "download_thread";
147 | private const int DEFAULT_MULTI_THREAD_DOWNLOAD = 3;
148 | private const string KEY_PRELOAD_IMAGE = "preload_image";
149 | private const int DEFAULT_PRELOAD_IMAGE = 5;
150 | private const string KEY_DOWNLOAD_ORIGIN_IMAGE = "download_origin_image";
151 | private const bool DEFAULT_DOWNLOAD_ORIGIN_IMAGE = false;
152 | private const string KEY_READ_THEME = "read_theme";
153 | private const int DEFAULT_READ_THEME = 1;
154 | /********************
155 | ****** Favorites
156 | ********************/
157 | private const string KEY_FAV_CAT_0 = "fav_cat_0";
158 | private const string KEY_FAV_CAT_1 = "fav_cat_1";
159 | private const string KEY_FAV_CAT_2 = "fav_cat_2";
160 | private const string KEY_FAV_CAT_3 = "fav_cat_3";
161 | private const string KEY_FAV_CAT_4 = "fav_cat_4";
162 | private const string KEY_FAV_CAT_5 = "fav_cat_5";
163 | private const string KEY_FAV_CAT_6 = "fav_cat_6";
164 | private const string KEY_FAV_CAT_7 = "fav_cat_7";
165 | private const string KEY_FAV_CAT_8 = "fav_cat_8";
166 | private const string KEY_FAV_CAT_9 = "fav_cat_9";
167 | private const string DEFAULT_FAV_CAT_0 = "Favorites 0";
168 | private const string DEFAULT_FAV_CAT_1 = "Favorites 1";
169 | private const string DEFAULT_FAV_CAT_2 = "Favorites 2";
170 | private const string DEFAULT_FAV_CAT_3 = "Favorites 3";
171 | private const string DEFAULT_FAV_CAT_4 = "Favorites 4";
172 | private const string DEFAULT_FAV_CAT_5 = "Favorites 5";
173 | private const string DEFAULT_FAV_CAT_6 = "Favorites 6";
174 | private const string DEFAULT_FAV_CAT_7 = "Favorites 7";
175 | private const string DEFAULT_FAV_CAT_8 = "Favorites 8";
176 | private const string DEFAULT_FAV_CAT_9 = "Favorites 9";
177 | private const string KEY_FAV_COUNT_0 = "fav_count_0";
178 | private const string KEY_FAV_COUNT_1 = "fav_count_1";
179 | private const string KEY_FAV_COUNT_2 = "fav_count_2";
180 | private const string KEY_FAV_COUNT_3 = "fav_count_3";
181 | private const string KEY_FAV_COUNT_4 = "fav_count_4";
182 | private const string KEY_FAV_COUNT_5 = "fav_count_5";
183 | private const string KEY_FAV_COUNT_6 = "fav_count_6";
184 | private const string KEY_FAV_COUNT_7 = "fav_count_7";
185 | private const string KEY_FAV_COUNT_8 = "fav_count_8";
186 | private const string KEY_FAV_COUNT_9 = "fav_count_9";
187 | private const string KEY_FAV_LOCAL = "fav_local";
188 | private const string KEY_FAV_CLOUD = "fav_cloud";
189 | private const int DEFAULT_FAV_COUNT = 0;
190 | private const string KEY_RECENT_FAV_CAT = "recent_fav_cat";
191 | // -1 for local, 0 - 9 for cloud favorite, other for no default fav slot
192 | private const string KEY_DEFAULT_FAV_SLOT = "default_favorite_2";
193 | private const int DEFAULT_DEFAULT_FAV_SLOT = INVALID_DEFAULT_FAV_SLOT;
194 | /********************
195 | ****** Analytics
196 | ********************/
197 | private const string KEY_ASK_ANALYTICS = "ask_analytics";
198 | private const bool DEFAULT_ASK_ANALYTICS = true;
199 | private const bool DEFAULT_ENABLE_ANALYTICS = false;
200 | private const string KEY_USER_ID = "user_id";
201 | private const string FILENAME_USER_ID = ".user_id";
202 | private const int LENGTH_USER_ID = 32;
203 | /********************
204 | ****** Update
205 | ********************/
206 | private const string KEY_BETA_UPDATE_CHANNEL = "beta_update_channel";
207 | private const bool DEFAULT_BETA_UPDATE_CHANNEL = false;
208 | private const string KEY_SKIP_UPDATE_VERSION = "skip_update_version";
209 | private const int DEFAULT_SKIP_UPDATE_VERSION = 0;
210 | private const bool DEFAULT_SAVE_PARSE_ERROR_BODY = true;
211 | private const string KEY_SAVE_CRASH_LOG = "save_crash_log";
212 | private const bool DEFAULT_SAVE_CRASH_LOG = true;
213 | private const bool DEFAULT_BUILT_IN_HOSTS = false;
214 | private const bool DEFAULT_FRONTING = false;
215 | private const string DEFAULT_APP_LANGUAGE = "system";
216 | private const string KEY_PROXY_TYPE = "proxy_type";
217 | private const int DEFAULT_PROXY_TYPE = 1;
218 | private const string KEY_PROXY_IP = "proxy_ip";
219 | private const string DEFAULT_PROXY_IP = null;
220 | private const string KEY_PROXY_PORT = "proxy_port";
221 | private const int DEFAULT_PROXY_PORT = -1;
222 | /********************
223 | ****** Guide
224 | ********************/
225 | private const string KEY_GUIDE_QUICK_SEARCH = "guide_quick_search";
226 | private const bool DEFAULT_GUIDE_QUICK_SEARCH = true;
227 | private const string KEY_GUIDE_COLLECTIONS = "guide_collections";
228 | private const bool DEFAULT_GUIDE_COLLECTIONS = true;
229 | private const string KEY_GUIDE_DOWNLOAD_THUMB = "guide_download_thumb";
230 | private const bool DEFAULT_GUIDE_DOWNLOAD_THUMB = true;
231 | private const string KEY_GUIDE_DOWNLOAD_LABELS = "guide_download_labels";
232 | private const bool DEFAULT_GUIDE_DOWNLOAD_LABELS = true;
233 | private const string KEY_GUIDE_GALLERY = "guide_gallery";
234 | private const bool DEFAULT_GUIDE_GALLERY = true;
235 | private const string KEY_CLIPBOARD_TEXT_HASH_CODE = "clipboard_text_hash_code";
236 | private const int DEFAULT_CLIPBOARD_TEXT_HASH_CODE = 0;
237 | private const string KEY_DOWNLOAD_DELAY = "download_delay";
238 | private const int DEFAULT_DOWNLOAD_DELAY = 0;
239 | private const string KEY_REQUEST_NEWS = "request_news";
240 | private const bool DEFAULT_REQUEST_NEWS = true;
241 |
242 | //Added by EHentaiAPI
243 | public const string KEY_SPIDER_FRONT_PRELOAD_COUNT = "spider_front_preload_count";
244 | public const string KEY_SPIDER_BACK_PRELOAD_COUNT = "spider_back_preload_count";
245 | public const int DEFAULT_SPIDER_FRONT_PRELOAD_COUNT = 5;
246 | public const int DEFAULT_SPIDER_BACK_PRELOAD_COUNT = 1;
247 |
248 | public ISharedPreferences SharedPreferences { get; set; } = new DefaultTemporaryMemorySharedPreferences();
249 |
250 | public bool GetBoolean(string key, bool defValue)
251 | {
252 | try
253 | {
254 | return SharedPreferences.getValue(key, defValue);
255 | }
256 | catch (InvalidCastException e)
257 | {
258 | Log.D(TAG, "Get ClassCastException when get " + key + " value", e);
259 | return defValue;
260 | }
261 | }
262 |
263 | public void SetBoolean(string key, bool value)
264 | {
265 | SharedPreferences.setValue(key, value);
266 | }
267 |
268 | public int GetInt(string key, int defValue)
269 | {
270 | try
271 | {
272 | return SharedPreferences.getValue(key, defValue);
273 | }
274 | catch (InvalidCastException e)
275 | {
276 | Log.D(TAG, "Get ClassCastException when get " + key + " value", e);
277 | return defValue;
278 | }
279 | }
280 |
281 | public void SetInt(string key, int value)
282 | {
283 | SharedPreferences.setValue(key, value);
284 | }
285 |
286 | public long GetLong(string key, long defValue)
287 | {
288 | try
289 | {
290 | return SharedPreferences.getValue(key, defValue);
291 | }
292 | catch (InvalidCastException e)
293 | {
294 | Log.D(TAG, "Get ClassCastException when get " + key + " value", e);
295 | return defValue;
296 | }
297 | }
298 |
299 | public void SetLong(string key, long value)
300 | {
301 | SharedPreferences.setValue(key, value);
302 | }
303 |
304 | public float GetFloat(string key, float defValue)
305 | {
306 | try
307 | {
308 | return SharedPreferences.getValue(key, defValue);
309 | }
310 | catch (InvalidCastException e)
311 | {
312 | Log.D(TAG, "Get ClassCastException when get " + key + " value", e);
313 | return defValue;
314 | }
315 | }
316 |
317 | public void SetFloat(string key, float value)
318 | {
319 | SharedPreferences.setValue(key, value);
320 | }
321 |
322 | public string GetString(string key, string defValue)
323 | {
324 | try
325 | {
326 | return SharedPreferences.getValue(key, defValue);
327 | }
328 | catch (InvalidCastException e)
329 | {
330 | Log.D(TAG, "Get ClassCastException when get " + key + " value", e);
331 | return defValue;
332 | }
333 | }
334 |
335 | public void SetString(string key, string value)
336 | {
337 | SharedPreferences.setValue(key, value);
338 | }
339 |
340 | public void PutIntToStr(string key, int value)
341 | {
342 | SharedPreferences.setValue(key, value.ToString());
343 | }
344 |
345 | public enum GallerySites
346 | {
347 | SITE_E = 0,
348 | SITE_EX = 1,
349 | }
350 |
351 | public GallerySites GallerySite
352 | {
353 | get { return (GallerySites)GetInt(KEY_GALLERY_SITE, DEFAULT_GALLERY_SITE); }
354 | set { SetInt(KEY_GALLERY_SITE, (int)value); }
355 | }
356 |
357 | public int SpiderFrontPreloadCount
358 | {
359 | get { return GetInt(KEY_SPIDER_FRONT_PRELOAD_COUNT, DEFAULT_SPIDER_FRONT_PRELOAD_COUNT); }
360 | set { SetInt(KEY_SPIDER_FRONT_PRELOAD_COUNT, value); }
361 | }
362 |
363 | public int SpiderBackPreloadCount
364 | {
365 | get { return GetInt(KEY_SPIDER_BACK_PRELOAD_COUNT, DEFAULT_SPIDER_BACK_PRELOAD_COUNT); }
366 | set { SetInt(KEY_SPIDER_BACK_PRELOAD_COUNT, value); }
367 | }
368 |
369 | public int ThumbResolution
370 | {
371 | get { return GetInt(KEY_THUMB_RESOLUTION, DEFAULT_THUMB_RESOLUTION); }
372 | set { SetInt(KEY_THUMB_RESOLUTION, value); }
373 | }
374 |
375 | public bool FixThumbUrl
376 | {
377 | get { return GetBoolean(KEY_FIX_THUMB_URL, DEFAULT_FIX_THUMB_URL); }
378 | set { SetBoolean(KEY_FIX_THUMB_URL, value); }
379 | }
380 | }
381 | }
382 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/Document.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 | using AngleSharp;
7 | using AngleSharp.Dom;
8 | using AngleSharp.Html.Dom;
9 | using AngleSharp.Html.Parser;
10 |
11 | namespace EHentaiAPI.Utils
12 | {
13 | public class Document
14 | {
15 | public IHtmlDocument document;
16 |
17 | public Document(string html)
18 | {
19 | var d = new HtmlParser();
20 | document = d.ParseDocument(html);
21 | }
22 |
23 | public IElement GetElementById(string v)
24 | {
25 | return document.GetElementById(v);
26 | }
27 |
28 | public static Document Parse(string html) => new Document(html);
29 |
30 | public IElement GetElementByClass(string v)
31 | {
32 | return GetElementsByClass(v).FirstOrDefault();
33 | }
34 |
35 | public IHtmlCollection GetElementsByClass(string v)
36 | {
37 | return document.GetElementsByClassName(v);
38 | }
39 |
40 | public IHtmlCollection Select(string cssQuery)
41 | {
42 | return document.QuerySelectorAll(cssQuery);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/EhPageImageSpider.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client;
2 | using EHentaiAPI.Client.Data;
3 | using EHentaiAPI.Client.Exceptions;
4 | using EHentaiAPI.ExtendFunction;
5 | using EHentaiAPI.Utils;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.ComponentModel;
9 | using System.IO;
10 | using System.Linq;
11 | using System.Text;
12 | using System.Threading;
13 | using System.Threading.Tasks;
14 |
15 | namespace EHentaiAPI.Utils
16 | {
17 | public class EhPageImageSpider
18 | {
19 | private static readonly string[] URL_509_SUFFIX_ARRAY = {
20 | "/509.gif",
21 | "/509s.gif"
22 | };
23 |
24 | public class SpiderInfo
25 | {
26 | public string ImageUrl { get; set; }
27 | public string SkipHathKey { get; set; }
28 | public string OriginImageUrl { get; set; }
29 | public string ShowKey { get; set; }
30 |
31 | public override string ToString() => $"ShowKey:{ShowKey} SkipHathKey:{SkipHathKey} OriginImageUrl:{OriginImageUrl}";
32 | }
33 |
34 | public class DownloadReporter
35 | {
36 | private readonly SpiderTask task;
37 |
38 | public DownloadReporter(SpiderTask task) => this.task = task;
39 |
40 | public long CurrentDownloadingLength
41 | {
42 | get { return task.CurrentDownloadingLength; }
43 | set
44 | {
45 | task.CurrentDownloadingLength = value;
46 | }
47 | }
48 |
49 | public long TotalDownloadLength
50 | {
51 | get { return task.TotalDownloadLength; }
52 | set
53 | {
54 | task.TotalDownloadLength = value;
55 | }
56 | }
57 |
58 | public override string ToString() => $"({1.0 * CurrentDownloadingLength / TotalDownloadLength * 100:F2}%) CurrentDownloadingLength:{CurrentDownloadingLength} TotalDownloadLength:{TotalDownloadLength}";
59 | }
60 |
61 | public class SpiderTask : INotifyPropertyChanged
62 | {
63 | public enum TaskStatus
64 | {
65 | NotStart,
66 | FetechingInfo,
67 | DownloadingImage,
68 | Finish,
69 | Error
70 | }
71 |
72 | public TaskStatus CurrentStatus
73 | {
74 | get
75 | {
76 | var previewTaskStatus = PreviewTask.Status;
77 | var downloadTaskStatus = PreviewTask.Status;
78 |
79 | if (previewTaskStatus == System.Threading.Tasks.TaskStatus.Faulted ||
80 | previewTaskStatus == System.Threading.Tasks.TaskStatus.Canceled ||
81 | downloadTaskStatus == System.Threading.Tasks.TaskStatus.Faulted ||
82 | downloadTaskStatus == System.Threading.Tasks.TaskStatus.Canceled)
83 | {
84 | return TaskStatus.Error;
85 | }
86 |
87 | if (previewTaskStatus != System.Threading.Tasks.TaskStatus.RanToCompletion)
88 | {
89 | return TaskStatus.FetechingInfo;
90 | }
91 |
92 | if (previewTaskStatus == System.Threading.Tasks.TaskStatus.RanToCompletion &&
93 | downloadTaskStatus != System.Threading.Tasks.TaskStatus.RanToCompletion)
94 | {
95 | return TaskStatus.DownloadingImage;
96 | }
97 |
98 | if (previewTaskStatus == System.Threading.Tasks.TaskStatus.RanToCompletion &&
99 | downloadTaskStatus == System.Threading.Tasks.TaskStatus.RanToCompletion)
100 | {
101 | return TaskStatus.Finish;
102 | }
103 |
104 | return TaskStatus.NotStart;
105 | }
106 | }
107 |
108 | internal TaskCompletionSource PreviewTaskSource { get; set; } = new();
109 | internal TaskCompletionSource DownloadTaskSouce { get; set; } = new();
110 |
111 | internal Task PreviewTask => PreviewTaskSource.Task;
112 | public Task DownloadTask => DownloadTaskSouce.Task;
113 |
114 | public GalleryPreview Preview { get; set; }
115 |
116 | private long currentDownloadingLength;
117 | public long CurrentDownloadingLength
118 | {
119 | get { return currentDownloadingLength; }
120 | set
121 | {
122 | currentDownloadingLength = value;
123 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(CurrentDownloadingLength)));
124 | }
125 | }
126 |
127 | private long totalDownloadLength;
128 | public long TotalDownloadLength
129 | {
130 | get { return totalDownloadLength; }
131 | set
132 | {
133 | totalDownloadLength = value;
134 | PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(TotalDownloadLength)));
135 | }
136 | }
137 |
138 | public event PropertyChangedEventHandler PropertyChanged;
139 |
140 | public override string ToString() => $"{CurrentStatus} ({1.0 * CurrentDownloadingLength / TotalDownloadLength * 100:F2}%) PreviewPosition:{Preview?.Position}";
141 | }
142 |
143 | public EhPageImageSpider(EhClient client, GalleryDetail detail, Func> imageDownloadFunc)
144 | {
145 | this.client = client;
146 | this.detail = detail;
147 | this.imageDownloadFunc = imageDownloadFunc;
148 | this.detail = detail;
149 | Previews = new(client, detail);
150 | }
151 |
152 | private Dictionary tasks = new();
153 | private readonly EhClient client;
154 | private readonly GalleryDetail detail;
155 | private readonly Func> imageDownloadFunc;
156 | private Dictionary skipHathKeys = new();
157 | public FullPreviewSetCollection Previews { get; private set; }
158 | private TaskCompletionSource showKey = new TaskCompletionSource();
159 |
160 | public SpiderTask RequestPage(int index)
161 | {
162 | if (tasks.TryGetValue(index, out var task))
163 | return task;
164 |
165 | var backCount = client.Settings.SpiderBackPreloadCount;
166 | var frontCount = client.Settings.SpiderFrontPreloadCount;
167 |
168 | for (int i = backCount; i >= 0; i--)
169 | PreloadPage(index - i);
170 | PreloadPage(index);
171 | for (int i = 1; i <= frontCount; i++)
172 | PreloadPage(index + i);
173 |
174 | return tasks[index];
175 | }
176 |
177 | private string GetPageUrl(long gid, int index, string pToken,
178 | string oldPageUrl, string skipHathKey)
179 | {
180 | string pageUrl;
181 | if (oldPageUrl != null)
182 | {
183 | pageUrl = oldPageUrl;
184 | }
185 | else
186 | {
187 | pageUrl = client.EhUrl.GetPageUrl(gid, index, pToken);
188 | }
189 | // Add skipHathKey
190 | if (skipHathKey != null)
191 | {
192 | if (pageUrl.Contains("?"))
193 | {
194 | pageUrl += "&nl=" + skipHathKey;
195 | }
196 | else
197 | {
198 | pageUrl += "?nl=" + skipHathKey;
199 | }
200 | }
201 | return pageUrl;
202 | }
203 |
204 | private string PickGoodSkipHashKey(int index)
205 | {
206 | return skipHathKeys.LastOrDefault(x => x.Key <= index).Value;
207 | }
208 |
209 | private async void PreloadPage(int index)
210 | {
211 | Action log = (msg) => Log.D($"EhPageImageSpider:{GetHashCode()}", $"{index}:{msg}");
212 |
213 | if (index < 0)
214 | return;
215 | if (index >= detail.Pages - 1)
216 | return;
217 | if (tasks.ContainsKey(index))
218 | return;
219 |
220 | var task = new SpiderTask();
221 | tasks[index] = task;
222 | log("Started");
223 |
224 | var prevTask = tasks.TryGetValue(index - 1, out var d) ? d : default;
225 | var curPreview = await Previews.GetAsync(index);
226 | task.Preview = curPreview;
227 |
228 | if (prevTask is null)
229 | {
230 | var skipHashKey = PickGoodSkipHashKey(index);
231 | var imageUrl = GetPageUrl(detail.Gid, index, curPreview.PToken, curPreview.PageUrl, skipHashKey);
232 | log($"call GetGalleryPageAsync() , PToken:{curPreview.PToken} , SkipHashKey:{skipHashKey} , PageUrl:{curPreview.PageUrl}");
233 | var pageResult = await client.GetGalleryPageAsync(detail, curPreview);
234 | if (URL_509_SUFFIX_ARRAY.Any(x => pageResult.imageUrl.EndsWith(x)))
235 | {
236 | task.DownloadTaskSouce.SetException(new EhException("509"));
237 | }
238 | else
239 | {
240 | var curSpiderInfo = new SpiderInfo()
241 | {
242 | ImageUrl = pageResult.imageUrl,
243 | OriginImageUrl = pageResult.originImageUrl,
244 | ShowKey = pageResult.showKey,
245 | SkipHathKey = pageResult.skipHathKey
246 | };
247 | if (!string.IsNullOrWhiteSpace(pageResult.skipHathKey))
248 | skipHathKeys[curPreview.Position] = pageResult.skipHathKey;
249 | task.PreviewTaskSource.SetResult(curSpiderInfo);
250 | if (!showKey.Task.IsCompleted)
251 | showKey.SetResult(curSpiderInfo.ShowKey);
252 | var obj = await imageDownloadFunc(curPreview, curSpiderInfo, new DownloadReporter(task));
253 | task.DownloadTaskSouce.SetResult(obj);
254 | }
255 | }
256 | else
257 | {
258 | var prevInfo = prevTask is null ? null : await prevTask.PreviewTask;
259 | var currentShowKey = prevInfo.ShowKey ?? await showKey.Task;
260 | log($"call GetGalleryPageApiAsync() , PToken:{curPreview.PToken} , showKey:{currentShowKey} , PreviousPToken:{prevTask.Preview.PToken}");
261 | var pageResult = await client.GetGalleryPageApiAsync(detail.Gid, curPreview.Position, curPreview.PToken, currentShowKey, prevTask.Preview.PToken);
262 | if (URL_509_SUFFIX_ARRAY.Any(x => pageResult.imageUrl.EndsWith(x)))
263 | {
264 | task.DownloadTaskSouce.SetException(new EhException("509"));
265 | return;
266 | }
267 | var curSpiderInfo = new SpiderInfo()
268 | {
269 | ImageUrl = pageResult.imageUrl,
270 | OriginImageUrl = pageResult.originImageUrl,
271 | ShowKey = currentShowKey,
272 | SkipHathKey = pageResult.skipHathKey
273 | };
274 | if (!string.IsNullOrWhiteSpace(pageResult.skipHathKey))
275 | skipHathKeys[curPreview.Position] = pageResult.skipHathKey;
276 | task.PreviewTaskSource.SetResult(curSpiderInfo);
277 | var obj = await imageDownloadFunc(curPreview, curSpiderInfo, new DownloadReporter(task));
278 | task.DownloadTaskSouce.SetResult(obj);
279 | }
280 |
281 | log("Finished");
282 | }
283 | }
284 | }
285 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/ExtensionMethods/AngleHtmlExtensionMethod.cs:
--------------------------------------------------------------------------------
1 | using AngleSharp.Dom;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Utils.ExtensionMethods
9 | {
10 | internal static class AngleHtmlExtensionMethod
11 | {
12 | public static IElement GetElementByIdRecursive(this IElement d, string id)
13 | {
14 | var queue = new Queue();
15 | queue.Enqueue(d);
16 |
17 | while (queue.Count != 0)
18 | {
19 | d = queue.Dequeue();
20 |
21 | if (d.Id == id)
22 | return d;
23 |
24 | foreach (var child in d.Children)
25 | {
26 | queue.Enqueue(child);
27 | }
28 | }
29 |
30 | return default;
31 | }
32 |
33 | public static string GetAttributeEx(this IElement d, string attr)
34 | {
35 | //jousp 那边如果没有此attr则返回空字符串,兼容一下
36 | return d.GetAttribute(attr) ?? string.Empty;
37 | }
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/ExtensionMethods/DateTimeExtensionMethod.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Utils.ExtensionMethods
8 | {
9 | internal static class DateTimeExtensionMethod
10 | {
11 | public static long ToUnixTimestamp(this DateTime dateTime)
12 | {
13 | return (dateTime.Ticks - 621355968000000000) / 10000000;
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/ExtensionMethods/ExceptionExtensionMethod.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.ExtendFunction;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Utils.ExtensionMethods
9 | {
10 | internal static class ExceptionExtensionMethod
11 | {
12 | public static void PrintStackTrace(this Exception e)
13 | {
14 | Log.E("PrintStackTrace", e.StackTrace);
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/ExtensionMethods/JsonExtensionMethod.cs:
--------------------------------------------------------------------------------
1 | using Newtonsoft.Json.Linq;
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using System.Text;
6 | using System.Threading.Tasks;
7 |
8 | namespace EHentaiAPI.Utils.ExtensionMethods
9 | {
10 | internal static class JsonExtensionMethod
11 | {
12 | public static string GetString(this JToken o, string key) => o[key].ToString();
13 | public static JArray GetJSONArray(this JToken o, string key) => o[key] as JArray;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/FullPreviewSetCollection.cs:
--------------------------------------------------------------------------------
1 | using EHentaiAPI.Client;
2 | using EHentaiAPI.Client.Data;
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Linq;
6 | using System.Text;
7 | using System.Threading;
8 | using System.Threading.Tasks;
9 |
10 | namespace EHentaiAPI.Utils
11 | {
12 | public class FullPreviewSetCollection : IAsyncEnumerable
13 | {
14 | private readonly EhClient client;
15 | private readonly GalleryDetail detail;
16 | private Dictionary cachedPreviews = new();
17 |
18 | public FullPreviewSetCollection(EhClient client, GalleryDetail detail)
19 | {
20 | this.client = client;
21 | this.detail = detail;
22 | }
23 |
24 |
25 | public async ValueTask GetAsync(int index, CancellationToken cancellationToken = default)
26 | {
27 | var d = GetAsyncEnumerator(cancellationToken);
28 | for (int i = 0; i <= index; i++)
29 | if (!await d.MoveNextAsync())
30 | return default;
31 | return d.Current;
32 | }
33 |
34 | public async IAsyncEnumerator GetAsyncEnumerator(CancellationToken cancellationToken = default)
35 | {
36 | var i = 0;
37 | var page = 0;
38 |
39 | while (i < detail.Pages)
40 | {
41 | if (cachedPreviews.TryGetValue(i, out var d))
42 | {
43 | yield return d;
44 | i++;
45 | }
46 | else
47 | {
48 | var item = await client.GetPreviewSetAsync(detail, page);
49 | if (item.Value == 0 || cancellationToken.IsCancellationRequested)
50 | {
51 | yield break;
52 | }
53 | else
54 | {
55 | page++;
56 |
57 | for (int w = 0; w < item.Key.Size; w++)
58 | {
59 | var preview = item.Key.GetGalleryPreview(detail.Gid, w);
60 | cachedPreviews[preview.Position] = preview;
61 | }
62 |
63 | if (cachedPreviews.TryGetValue(i, out d))
64 | {
65 | yield return d;
66 | i++;
67 | }
68 | }
69 | }
70 | }
71 | }
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/ISharedPreferences.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Utils
8 | {
9 | public interface ISharedPreferences
10 | {
11 | public T getValue(string key, T defValue = default);
12 | public ISharedPreferences setValue(string key, T value = default);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/SharedPreferences.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Utils
8 | {
9 | internal class DefaultTemporaryMemorySharedPreferences : ISharedPreferences
10 | {
11 | private Dictionary preferences = new();
12 |
13 | public T getValue(string key, T defValue = default) => preferences.TryGetValue(key, out var d) ? (T)d : defValue;
14 | public ISharedPreferences setValue(string key, T value = default)
15 | {
16 | preferences[key] = value;
17 | return this;
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/EHentaiAPI/Utils/UrlBuilder.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Linq;
4 | using System.Text;
5 | using System.Threading.Tasks;
6 |
7 | namespace EHentaiAPI.Utils
8 | {
9 | public class UrlBuilder
10 | {
11 |
12 | public string mRootUrl;
13 | public Dictionary mQueryMap = new();
14 |
15 | public UrlBuilder(string rootUrl)
16 | {
17 | mRootUrl = rootUrl;
18 | }
19 |
20 | public void AddQuery(string key, Object value)
21 | {
22 | mQueryMap.Add(key, value);
23 | }
24 |
25 | public string Build()
26 | {
27 | if (mQueryMap.Count == 0)
28 | {
29 | return mRootUrl;
30 | }
31 | else
32 | {
33 | StringBuilder sb = new StringBuilder(mRootUrl);
34 | sb.Append('?');
35 |
36 | var c = 0;
37 | foreach (var pair in mQueryMap)
38 | {
39 | if (c != 0)
40 | sb.Append('&');
41 | sb.Append(pair.Key).Append('=').Append(pair.Value);
42 | c++;
43 | }
44 | return sb.ToString();
45 | }
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Wbooru Project
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # EHentaiAPI
2 | .Net porting implementation of EhViewer
3 |
4 | # 注意
5 | 此项目是从[EhViewer](https://github.com/seven332/EhViewer)(实际采用[NekoInverter](https://gitlab.com/NekoInverter/EhViewer)版本)项目,里面的EH相关请求API移植并独立出来的。
6 | 此项目将用于Wbooru的新的插件项目,如果可以我也会继续维护以及开发相关功能。
7 |
8 | # 如何食用?
9 | [](https://www.nuget.org/packages/EHentaiAPI)
10 | * [API接口](https://github.com/MikiraSora/EHentaiAPI/blob/master/EHentaiAPI/Client/EhClient.cs#L92)
11 | * [实际使用](https://github.com/MikiraSora/EHentaiAPI/blob/master/EHentaiAPI.TestConsole/Program.cs)
12 | * 也提供部分接口的单元测试来确保此轮子基本功能是否正常
13 |
14 | # 计划
15 | * 优化调用过程
16 | * 将部分接口的常量改成枚举形式
17 | * 留出更多的可定制的接口
18 | * 整更多的接口(如果Wbooru需要)
19 |
--------------------------------------------------------------------------------