├── .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(request); 75 | 76 | public async Task Execute(EhRequest request) 77 | { 78 | if (!request.IsCancelled) 79 | { 80 | var task = new EhTask(request.Method, EhUrl, request.Callback, Cookies); 81 | task.ExecuteOnExecutor(request.Args); 82 | request.SetTask(task); 83 | var r = await task.Task; 84 | if (r is Exception e) 85 | throw e; 86 | return (T)r; 87 | } 88 | else 89 | { 90 | request.Callback.OnCancel(); 91 | return default; 92 | } 93 | } 94 | 95 | #region New API Usages 96 | 97 | public Task SignInAsync(string username, string password) 98 | => Execute(new EhRequest() 99 | .SetArgs(username, password) 100 | .SetMethod(Method.METHOD_SIGN_IN)); 101 | 102 | public Task GetGalleryListAsync(string url) 103 | => Execute(new EhRequest() 104 | .SetArgs(url) 105 | .SetMethod(Method.METHOD_GET_GALLERY_LIST)); 106 | 107 | public Task GetGalleryDetailAsync(string detailUrl) 108 | => Execute(new EhRequest() 109 | .SetArgs(detailUrl) 110 | .SetMethod(Method.METHOD_GET_GALLERY_DETAIL)); 111 | 112 | public Task VoteCommentAsync(GalleryDetail detail, GalleryComment comment, int vote) 113 | => VoteCommentAsync(detail.ApiUid, detail.ApiKey, detail.Gid, detail.Token, comment.Id, vote); 114 | 115 | public Task VoteCommentAsync(long apiUid, string apiKey, long gid, string token, long commentId, int commentVote) 116 | => Execute(new EhRequest() 117 | .SetArgs(apiUid, apiKey, gid, token, commentId, commentVote) 118 | .SetMethod(Method.METHOD_VOTE_COMMENT)); 119 | 120 | public Task> GetTorrentListAsync(GalleryDetail detail) 121 | => GetTorrentListAsync(detail.TorrentUrl, detail.Gid, detail.Token); 122 | 123 | public Task> GetTorrentListAsync(string torrentUrl, long gid, string token) 124 | => Execute>(new EhRequest() 125 | .SetArgs(torrentUrl, gid, token) 126 | .SetMethod(Method.METHOD_GET_TORRENT_LIST)); 127 | 128 | public Task[]>> GetArchiveListAsync(GalleryDetail detail) 129 | => GetArchiveListAsync(detail.ArchiveUrl, detail.Gid, detail.Token); 130 | 131 | public Task[]>> GetArchiveListAsync(string archiveUrl, long gid, string token) 132 | => Execute[]>>(new EhRequest() 133 | .SetArgs(archiveUrl, gid, token) 134 | .SetMethod(Method.METHOD_ARCHIVE_LIST)); 135 | 136 | public Task VoteTagAsync(GalleryDetail detail, string tags, int vote) 137 | => VoteTagAsync(detail.ApiUid, detail.ApiKey, detail.Gid, detail.Token, tags, vote); 138 | 139 | public Task VoteTagAsync(long apiUid, string apiKey, long gid, string token, string tags, int vote) 140 | => Execute(new EhRequest() 141 | .SetArgs(apiUid, apiKey, gid, token, tags, vote) 142 | .SetMethod(Method.METHOD_VOTE_TAG)); 143 | 144 | /// 145 | /// 146 | /// 147 | /// 148 | /// 1~5 , also 3.5,etc. 149 | /// 150 | public Task RateGalleryAsync(GalleryDetail detail, float rating) 151 | => RateGalleryAsync(detail.ApiUid, detail.ApiKey, detail.Gid, detail.Token, rating); 152 | 153 | /// 154 | /// 155 | /// 156 | /// 157 | /// 1~5 , also 3.5,etc. 158 | /// 159 | public Task RateGalleryAsync(long apiUid, string apiKey, long gid, string token, float rating) 160 | => Execute(new EhRequest() 161 | .SetArgs(apiUid, apiKey, gid, token, rating) 162 | .SetMethod(Method.METHOD_GET_RATE_GALLERY)); 163 | 164 | public Task CommentNewGalleryAsync(GalleryDetail detail, string commentContent) 165 | => CommentNewGalleryAsync(detail.Url, commentContent); 166 | 167 | public Task CommentNewGalleryAsync(string url, string commentContent) 168 | => Execute(new EhRequest() 169 | .SetArgs(url, commentContent, null) 170 | .SetMethod(Method.METHOD_GET_COMMENT_GALLERY)); 171 | 172 | public Task ModifyCommentGalleryAsync(GalleryDetail detail, string newCommentContent, GalleryComment comment) 173 | => ModifyCommentGalleryAsync(detail.Url, newCommentContent, comment.Id); 174 | 175 | public Task ModifyCommentGalleryAsync(string url, string commentContent, long commendId) 176 | => Execute(new EhRequest() 177 | .SetArgs(url, commentContent, commendId) 178 | .SetMethod(Method.METHOD_GET_COMMENT_GALLERY)); 179 | 180 | /// 181 | /// 通过具体页面url获取画廊的token,比如 182 | /// https://e-hentai.org/s/7b13386d6b/2062872-10 183 | /// 184 | /// 具体页面url 185 | /// 186 | public Task GetGalleryTokenAsync(string url) 187 | { 188 | var result2 = GalleryPageUrlParser.Parse(url, false); 189 | return GetGalleryTokenAsync(result2.gid, result2.pToken, result2.page); 190 | } 191 | 192 | /// 193 | /// 通过具体页面url获取画廊的token,比如 194 | /// https://e-hentai.org/s/7b13386d6b/2062872-10 195 | /// 196 | /// 2062872 197 | /// 7b13386d6b 198 | /// 10 199 | /// token fb6abc76c6 200 | public Task GetGalleryTokenAsync(long gid, string gtoken, int page) 201 | => Execute(new EhRequest() 202 | .SetArgs(gid, gtoken, page) 203 | .SetMethod(Method.METHOD_GET_GALLERY_TOKEN)); 204 | 205 | /// 206 | /// 获取指定的收藏列表 207 | /// 208 | /// 获取列表的url地址,比如https://e-hentai.org/favorites.php?favcat=all&page=2 ,可通过FavListUrlBuilder快速构造 209 | /// 210 | public Task GetFavoritesAsync(string url) 211 | => Execute(new EhRequest() 212 | .SetArgs(url) 213 | .SetMethod(Method.METHOD_GET_FAVORITES)); 214 | 215 | public Task GetFavoritesAsync(FavListUrlBuilder urlBuilder) 216 | => GetFavoritesAsync(urlBuilder.Build()); 217 | 218 | public Task RemoveFavoriteAsync(GalleryDetail detail) 219 | => RemoveFavoriteAsync(detail.Gid, detail.Token); 220 | 221 | public Task RemoveFavoriteAsync(long gid, string token) 222 | => Execute(new EhRequest() 223 | .SetArgs(gid, token, -1, string.Empty) 224 | .SetMethod(Method.METHOD_ADD_FAVORITES)); 225 | 226 | public Task AddFavoriteAsync(GalleryDetail detail, int dstCat, string note) 227 | => AddFavoriteAsync(detail.Gid, detail.Token, dstCat, note); 228 | 229 | public Task AddFavoriteAsync(long gid, string token, int dstCat, string note) 230 | => Execute(new EhRequest() 231 | .SetArgs(gid, token, dstCat, note) 232 | .SetMethod(Method.METHOD_ADD_FAVORITES)); 233 | 234 | public Task AddFavoritesRangeAsync(long[] gidArray, string[] tokenArray, int dstCat) 235 | => Execute(new EhRequest() 236 | .SetArgs(gidArray, tokenArray, dstCat) 237 | .SetMethod(Method.METHOD_ADD_FAVORITES_RANGE)); 238 | 239 | public Task ModifyFavoritesAsync(FavListUrlBuilder urlBuilder, long[] gidArray, int dstCat) 240 | => ModifyFavoritesAsync(urlBuilder.Build(), gidArray, dstCat); 241 | 242 | public Task ModifyFavoritesAsync(string url, long[] gidArray, int dstCat) 243 | => Execute(new EhRequest() 244 | .SetArgs(url, gidArray, dstCat) 245 | .SetMethod(Method.METHOD_MODIFY_FAVORITES)); 246 | 247 | public Task GetProfileAsync() 248 | => Execute(new EhRequest() 249 | .SetMethod(Method.METHOD_GET_PROFILE)); 250 | 251 | public Task> GetPreviewSetAsync(GalleryDetail detail, int index) 252 | => GetPreviewSetAsync(detail.Gid, detail.Token, index); 253 | 254 | public Task> GetPreviewSetAsync(long gid, string token, int index) 255 | => GetPreviewSetAsync(EhUrl.GetGalleryDetailUrl(gid, token, index, false)); 256 | 257 | public Task> GetPreviewSetAsync(string url) 258 | => Execute>(new EhRequest() 259 | .SetArgs(url) 260 | .SetMethod(Method.METHOD_GET_PREVIEW_SET)); 261 | 262 | public Task GetGalleryPageAsync(GalleryDetail detail,GalleryPreview preview) 263 | => GetGalleryPageAsync(preview.PageUrl, detail.Gid, detail.Token); 264 | 265 | public Task GetGalleryPageAsync(string pageUrl, long gid, string token) 266 | => Execute(new EhRequest() 267 | .SetArgs(pageUrl, gid, token) 268 | .SetMethod(Method.METHOD_GET_GALLERY_PAGE)); 269 | 270 | public Task GetGalleryPageApiAsync(long gid, int index, string pToken, string showKey, string previousPToken) 271 | => Execute(new EhRequest() 272 | .SetArgs(gid, index, pToken, showKey, previousPToken) 273 | .SetMethod(Method.METHOD_GET_GALLERY_PAGE_API)); 274 | 275 | #endregion 276 | } 277 | } 278 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/EhConfig.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 class EhConfig 10 | { 11 | /** 12 | * The Cookie key of uconfig 13 | */ 14 | public const string KEY_UCONFIG = "uconfig"; 15 | /** 16 | * The Cookie key of lofi resolution 17 | */ 18 | public const string KEY_LOFI_RESOLUTION = "xres"; 19 | /** 20 | * The Cookie key of show warning 21 | */ 22 | public const string KEY_CONTENT_WARNING = "nw"; 23 | /** 24 | * load images through the Hentai@Home Network 25 | */ 26 | public const string LOAD_FROM_HAH_YES = "y"; 27 | /** 28 | * do not load images through the Hentai@Home Network 29 | */ 30 | public const string LOAD_FROM_HAH_NO = "n"; 31 | /** 32 | * Image Size Auto 33 | */ 34 | public const string IMAGE_SIZE_AUTO = "a"; 35 | /** 36 | * Image Size 780x 37 | */ 38 | public const string IMAGE_SIZE_780X = "780"; 39 | /** 40 | * Image Size 980x 41 | */ 42 | public const string IMAGE_SIZE_980X = "980"; 43 | /** 44 | * Image Size 1280x 45 | */ 46 | public const string IMAGE_SIZE_1280X = "1280"; 47 | /** 48 | * Image Size 1600x 49 | */ 50 | public const string IMAGE_SIZE_1600X = "1600"; 51 | /** 52 | * Image Size 2400x 53 | */ 54 | public const string IMAGE_SIZE_2400X = "2400"; 55 | /** 56 | * Manual Accept, Manual Start 57 | */ 58 | public const string ARCHIVER_DOWNLOAD_MAMS = "0"; 59 | /** 60 | * >Manual Accept, Auto Start 61 | */ 62 | public const string ARCHIVER_DOWNLOAD_AAMS = "1"; 63 | /** 64 | * Auto Accept, Manual Start 65 | */ 66 | public const string ARCHIVER_DOWNLOAD_MAAS = "2"; 67 | /** 68 | * Auto Accept, Auto Start 69 | */ 70 | public const string ARCHIVER_DOWNLOAD_AAAS = "3"; 71 | /** 72 | * List View on the front and search pages 73 | */ 74 | public const string LAYOUT_MODE_LIST = "l"; 75 | /** 76 | * Thumbnail View on the front and search pages 77 | */ 78 | public const string LAYOUT_MODE_THUMB = "t"; 79 | public const int MISC = 0x1; 80 | public const int DOUJINSHI = 0x2; 81 | public const int MANGA = 0x4; 82 | public const int ARTIST_CG = 0x8; 83 | public const int GAME_CG = 0x10; 84 | public const int IMAGE_SET = 0x20; 85 | public const int COSPLAY = 0x40; 86 | public const int ASIAN_PORN = 0x80; 87 | public const int NON_H = 0x100; 88 | public const int WESTERN = 0x200; 89 | public const int ALL_CATEGORY = 0x3ff; 90 | public const int NAMESPACES_RECLASS = 0x1; 91 | public const int NAMESPACES_LANGUAGE = 0x2; 92 | public const int NAMESPACES_PARODY = 0x4; 93 | public const int NAMESPACES_CHARACTER = 0x8; 94 | public const int NAMESPACES_GROUP = 0x10; 95 | public const int NAMESPACES_ARTIST = 0x20; 96 | public const int NAMESPACES_MALE = 0x40; 97 | public const int NAMESPACES_FEMALE = 0x80; 98 | public const string JAPANESE_ORIGINAL = "0"; 99 | public const string JAPANESE_TRANSLATED = "1024"; 100 | public const string JAPANESE_REWRITE = "2048"; 101 | public const string ENGLISH_ORIGINAL = "1"; 102 | public const string ENGLISH_TRANSLATED = "1025"; 103 | public const string ENGLISH_REWRITE = "2049"; 104 | public const string CHINESE_ORIGINAL = "10"; 105 | public const string CHINESE_TRANSLATED = "1034"; 106 | public const string CHINESE_REWRITE = "2058"; 107 | public const string DUTCH_ORIGINAL = "20"; 108 | public const string DUTCH_TRANSLATED = "1044"; 109 | public const string DUTCH_REWRITE = "2068"; 110 | public const string FRENCH_ORIGINAL = "30"; 111 | public const string FRENCH_TRANSLATED = "1054"; 112 | public const string FRENCH_REWRITE = "2078"; 113 | public const string GERMAN_ORIGINAL = "40"; 114 | public const string GERMAN_TRANSLATED = "1064"; 115 | public const string GERMAN_REWRITE = "2088"; 116 | public const string HUNGARIAN_ORIGINAL = "50"; 117 | public const string HUNGARIAN_TRANSLATED = "1074"; 118 | public const string HUNGARIAN_REWRITE = "2098"; 119 | public const string ITALIAN_ORIGINAL = "60"; 120 | public const string ITALIAN_TRANSLATED = "1084"; 121 | public const string ITALIAN_REWRITE = "2108"; 122 | public const string KOREAN_ORIGINAL = "70"; 123 | public const string KOREAN_TRANSLATED = "1094"; 124 | public const string KOREAN_REWRITE = "2118"; 125 | public const string POLISH_ORIGINAL = "80"; 126 | public const string POLISH_TRANSLATED = "1104"; 127 | public const string POLISH_REWRITE = "2128"; 128 | public const string PORTUGUESE_ORIGINAL = "90"; 129 | public const string PORTUGUESE_TRANSLATED = "1114"; 130 | public const string PORTUGUESE_REWRITE = "2138"; 131 | public const string RUSSIAN_ORIGINAL = "100"; 132 | public const string RUSSIAN_TRANSLATED = "1124"; 133 | public const string RUSSIAN_REWRITE = "2148"; 134 | public const string SPANISH_ORIGINAL = "110"; 135 | public const string SPANISH_TRANSLATED = "1134"; 136 | public const string SPANISH_REWRITE = "2158"; 137 | public const string THAI_ORIGINAL = "120"; 138 | public const string THAI_TRANSLATED = "1144"; 139 | public const string THAI_REWRITE = "2168"; 140 | public const string VIETNAMESE_ORIGINAL = "130"; 141 | public const string VIETNAMESE_TRANSLATED = "1154"; 142 | public const string VIETNAMESE_REWRITE = "2178"; 143 | public const string NA_ORIGINAL = "254"; 144 | public const string NA_TRANSLATED = "1278"; 145 | public const string NA_REWRITE = "2302"; 146 | public const string OTHER_ORIGINAL = "255"; 147 | public const string OTHER_TRANSLATED = "1279"; 148 | public const string OTHER_REWRITE = "2303"; 149 | /** 150 | * 25 results per page for the index/search page and torrent search pages 151 | */ 152 | public const string RESULT_COUNT_25 = "0"; 153 | /** 154 | * 50 results per page for the index/search page and torrent search pages 155 | */ 156 | public const string RESULT_COUNT_50 = "1"; 157 | /** 158 | * 100 results per page for the index/search page and torrent search pages 159 | */ 160 | public const string RESULT_COUNT_100 = "2"; 161 | /** 162 | * 200 results per page for the index/search page and torrent search pages 163 | */ 164 | public const string RESULT_COUNT_200 = "3"; 165 | /** 166 | * On mouse-over 167 | */ 168 | public const string MOUSE_OVER_YES = "m"; 169 | /** 170 | * On page load 171 | */ 172 | public const string MOUSE_OVER_NO = "p"; 173 | /** 174 | * Preview normal size 175 | */ 176 | public const string PREVIEW_SIZE_NORMAL = "m"; 177 | /** 178 | * Preview large size 179 | */ 180 | public const string PREVIEW_SIZE_LARGE = "l"; 181 | /** 182 | * 4 row preview per page 183 | */ 184 | public const string PREVIEW_ROW_4 = "2"; 185 | /** 186 | * 10 row preview per page 187 | */ 188 | public const string PREVIEW_ROW_10 = "5"; 189 | /** 190 | * 20 row preview per page 191 | */ 192 | public const string PREVIEW_ROW_20 = "10"; 193 | /** 194 | * 40 row preview per page 195 | */ 196 | public const string PREVIEW_ROW_40 = "20"; 197 | /** 198 | * Oldest comments first 199 | */ 200 | public const string COMMENTS_SORT_OLDEST_FIRST = "a"; 201 | /** 202 | * Recent comments first 203 | */ 204 | public const string COMMENTS_SORT_RECENT_FIRST = "d"; 205 | /** 206 | * By highest score 207 | */ 208 | public const string COMMENTS_SORT_HIGHEST_SCORE_FIRST = "s"; 209 | /** 210 | * Show gallery comment votes On score hover or click 211 | */ 212 | public const string COMMENTS_VOTES_POP = "0"; 213 | /** 214 | * Always show gallery comment votes 215 | */ 216 | public const string COMMENTS_VOTES_ALWAYS = "1"; 217 | /** 218 | * Sort order for gallery tags alphabetically 219 | */ 220 | public const string TAGS_SORT_ALPHABETICAL = "a"; 221 | /** 222 | * Sort order for gallery tags by tag power 223 | */ 224 | public const string TAGS_SORT_POWER = "p"; 225 | /** 226 | * Show gallery page numbers 227 | */ 228 | public const string SHOW_GALLERY_INDEX_YES = "1"; 229 | /** 230 | * Do not show gallery page numbers 231 | */ 232 | public const string SHOW_GALLERY_INDEX_NO = "0"; 233 | /** 234 | * Enable Tag Flagging 235 | */ 236 | public const string ENABLE_TAG_FLAGGING_YES = "y"; 237 | /** 238 | * Do not enable Tag Flagging 239 | */ 240 | public const string ENABLE_TAG_FLAGGING_NO = "n"; 241 | /** 242 | * Always display the original images 243 | */ 244 | public const string ALWAYS_ORIGINAL_YES = "y"; 245 | /** 246 | * Do not Always display the original images 247 | */ 248 | public const string ALWAYS_ORIGINAL_NO = "n"; 249 | /** 250 | * Enable the Multi-Page Viewe 251 | */ 252 | public const string MULTI_PAGE_YES = "y"; 253 | /** 254 | * Do not enable the Multi-Page Viewe 255 | */ 256 | public const string MULTI_PAGE_NO = "n"; 257 | /** 258 | * Align left, only scale if image is larger than browser width 259 | */ 260 | public const string MULTI_PAGE_STYLE_N = "n"; 261 | /** 262 | * Align center, only scale if image is larger than browser width 263 | */ 264 | public const string MULTI_PAGE_STYLE_C = "c"; 265 | /** 266 | * Align center, Always scale images to fit browser width 267 | */ 268 | public const string MULTI_PAGE_STYLE_Y = "y"; 269 | /** 270 | * Show Multi-Page Viewer Thumbnail Pane 271 | */ 272 | public const string MULTI_PAGE_THUMB_SHOW = "n"; 273 | /** 274 | * Hide Multi-Page Viewer Thumbnail Pane 275 | */ 276 | public const string MULTI_PAGE_THUMB_HIDE = "y"; 277 | /** 278 | * 460x for lofi resolution 279 | */ 280 | public const string LOFI_RESOLUTION_460X = "1"; 281 | /** 282 | * 780X for lofi resolution 283 | */ 284 | public const string LOFI_RESOLUTION_780X = "2"; 285 | /** 286 | * 980X for lofi resolution 287 | */ 288 | public const string LOFI_RESOLUTION_980X = "3"; 289 | /** 290 | * show warning 291 | */ 292 | public const string CONTENT_WARNING_SHOW = "0"; 293 | /** 294 | * not show warning 295 | */ 296 | public const string CONTENT_WARNING_NOT_SHOW = "1"; 297 | /** 298 | * Default gallery title 299 | */ 300 | private const string GALLERY_TITLE_DEFAULT = "r"; 301 | /** 302 | * Show popular 303 | */ 304 | private const string POPULAR_YES = "y"; 305 | /** 306 | * Sort favorites by last gallery update time 307 | */ 308 | private const string FAVORITES_SORT_FAVORITED_TIME = "f"; 309 | /** 310 | * Load images through the Hentai@Home Network
311 | * key: {@link #KEY_LOAD_FROM_HAH}
312 | * value: {@link #LOAD_FROM_HAH_YES}, {@link #LOAD_FROM_HAH_NO} 313 | */ 314 | public string loadFromHAH = LOAD_FROM_HAH_YES; 315 | 316 | /** 317 | * Image Size
318 | * key: {@link #KEY_IMAGE_SIZE}
319 | * value: {@link #IMAGE_SIZE_AUTO}, {@link #IMAGE_SIZE_780X}, {@link #IMAGE_SIZE_980X}, 320 | * {@link #IMAGE_SIZE_1280X}, {@link #IMAGE_SIZE_1600X}, {@link #IMAGE_SIZE_2400X} 321 | */ 322 | public string imageSize = IMAGE_SIZE_AUTO; 323 | 324 | /** 325 | * Scale width
326 | * key: {@link #KEY_SCALE_WIDTH}
327 | * value: 0 for no limit 328 | */ 329 | public int scaleWidth = 0; 330 | 331 | /** 332 | * Scale height
333 | * key: {@link #KEY_SCALE_HEIGHT}
334 | * value: 0 for no limit 335 | */ 336 | public int scaleHeight = 0; 337 | 338 | /** 339 | * Gallery title
340 | * key: {@link #KEY_GALLERY_TITLE}
341 | * value: {@link #GALLERY_TITLE_DEFAULT}, {@link #GALLERY_TITLE_JAPANESE} 342 | */ 343 | public string galleryTitle = GALLERY_TITLE_DEFAULT; 344 | 345 | /** 346 | * The default behavior for downloading an archiver
347 | * key: {@link #KEY_ARCHIVER_DOWNLOAD}
348 | * value: {@link #ARCHIVER_DOWNLOAD_MAMS}, {@link #ARCHIVER_DOWNLOAD_AAMS}, 349 | * {@link #ARCHIVER_DOWNLOAD_MAAS}, {@link #ARCHIVER_DOWNLOAD_AAAS} 350 | */ 351 | public string archiverDownload = ARCHIVER_DOWNLOAD_MAMS; 352 | 353 | /** 354 | * Display mode used on the front and search pages
355 | * false for list, true for thumb
356 | * key: {@link #KEY_LAYOUT_MODE}
357 | * value: {@link #LAYOUT_MODE_LIST}, {@link #LAYOUT_MODE_THUMB} 358 | */ 359 | public string layoutMode = LAYOUT_MODE_LIST; 360 | 361 | /** 362 | * Show popular or not
363 | * key: {@link #KEY_POPULAR}
364 | * value: {@link #POPULAR_YES}, {@link #POPULAR_NO} 365 | */ 366 | public string popular = POPULAR_YES; 367 | 368 | /** 369 | * Default categories on the front page
370 | * key: {@link #KEY_DEFAULT_CATEGORIES}
371 | * value: the value of categories, for multiple use & operation, 372 | * 0 for none 373 | */ 374 | public int defaultCategories = 0; 375 | 376 | /** 377 | *
378 | * key: {@link #KEY_FAVORITES_SORT}
379 | * value: {@link #FAVORITES_SORT_GALLERY_UPDATE_TIME}, {@link #FAVORITES_SORT_FAVORITED_TIME} 380 | */ 381 | public string favoritesSort = FAVORITES_SORT_FAVORITED_TIME; 382 | 383 | /** 384 | * Certain namespaces excluded from a default tag search
385 | * key: {@link #KEY_EXCLUDED_NAMESPACES}
386 | * value: the value of namespaces, for multiple use & operation, 387 | * 0 for none 388 | */ 389 | public int excludedNamespaces = 0; 390 | 391 | /** 392 | * Certain languages excluded from list and searches
393 | * key: {@link #KEY_EXCLUDED_LANGUAGES}
394 | * value: {@link #JAPANESE_TRANSLATED}, {@link #JAPANESE_REWRITE} ... 395 | * For multiple languages, use x to combine them, like 1x1024x2048 396 | */ 397 | public string excludedLanguages = ""; 398 | 399 | /** 400 | * How many results would you like per page for the index/search page 401 | * and torrent search pages
402 | * key: {@link #KEY_RESULT_COUNT}
403 | * value: {@link #RESULT_COUNT_25}, {@link #RESULT_COUNT_50}, 404 | * {@link #RESULT_COUNT_100}, {@link #RESULT_COUNT_200}
405 | * Require Hath Perk:Paging Enlargement 406 | */ 407 | public string resultCount = RESULT_COUNT_25; 408 | 409 | /** 410 | * mouse-over thumb
411 | * key: {@link #KEY_MOUSE_OVER}
412 | * value: {@link #MOUSE_OVER_YES}, {@link #MOUSE_OVER_NO} 413 | */ 414 | public string mouseOver = MOUSE_OVER_YES; 415 | 416 | /** 417 | * Default preview mode
418 | * key: {@link #KEY_PREVIEW_SIZE}
419 | * value: {@link #PREVIEW_SIZE_NORMAL}, {@link #PREVIEW_SIZE_LARGE} 420 | */ 421 | public string previewSize = PREVIEW_SIZE_LARGE; 422 | 423 | /** 424 | * Preview row
425 | * key: {@link #KEY_PREVIEW_ROW}
426 | * value: {@link #PREVIEW_ROW_4}, {@link #PREVIEW_ROW_10}, 427 | * {@link #PREVIEW_ROW_20}, {@link #PREVIEW_ROW_40} 428 | */ 429 | public string previewRow = PREVIEW_ROW_4; 430 | 431 | /** 432 | * Sort order for gallery comments
433 | * key: {@link #KEY_COMMENTS_SORT}
434 | * value: {@link #COMMENTS_SORT_OLDEST_FIRST}, {@link #COMMENTS_SORT_RECENT_FIRST}, 435 | * {@link #COMMENTS_SORT_HIGHEST_SCORE_FIRST} 436 | */ 437 | public string commentSort = COMMENTS_SORT_OLDEST_FIRST; 438 | 439 | /** 440 | * Show gallery comment votes mode
441 | * key: {@link #KEY_COMMENTS_VOTES}
442 | * value: {@link #COMMENTS_VOTES_POP}, {@link #COMMENTS_VOTES_ALWAYS} 443 | */ 444 | public string commentVotes = COMMENTS_VOTES_POP; 445 | 446 | 447 | /** 448 | * Sort order for gallery tags
449 | * key: {@link #KEY_TAGS_SORT}
450 | * value: {@link #TAGS_SORT_ALPHABETICAL}, {@link #TAGS_SORT_POWER} 451 | */ 452 | public string tagSort = TAGS_SORT_ALPHABETICAL; 453 | 454 | /** 455 | * Show gallery page numbers
456 | * key: {@link #KEY_SHOW_GALLERY_INDEX}
457 | * value: {@link #SHOW_GALLERY_INDEX_YES}, {@link #SHOW_GALLERY_INDEX_NO} 458 | */ 459 | public string showGalleryIndex = SHOW_GALLERY_INDEX_YES; 460 | 461 | /** 462 | * The IP of a proxy-enabled Hentai@Home Client 463 | * to load all images
464 | * key: {@link #KEY_HAH_CLIENT_IP_PORT}
465 | */ 466 | public string hahClientIp = ""; 467 | 468 | /** 469 | * The PORT of a proxy-enabled Hentai@Home Client 470 | * to load all images
471 | * key: {@link #KEY_HAH_CLIENT_IP_PORT}
472 | */ 473 | public int hahClientPort = -1; 474 | 475 | /** 476 | * The passkey of a proxy-enabled Hentai@Home Client 477 | * to load all images
478 | * key: {@link #KEY_HAH_CLIENT_PASSKEY}
479 | */ 480 | public string hahClientPasskey = ""; 481 | 482 | /** 483 | * Enable tag flagging 484 | * key: {@link #KEY_ENABLE_TAG_FLAGGING}
485 | * value: {@link #ENABLE_TAG_FLAGGING_YES}, {@link #ENABLE_TAG_FLAGGING_NO}
486 | * Bronze Star or Hath Perk: Tag Flagging Required 487 | */ 488 | public string enableTagFlagging = ENABLE_TAG_FLAGGING_NO; 489 | 490 | /** 491 | * Always display the original images instead of the resampled versions
492 | * key: {@link #KEY_ALWAYS_ORIGINAL}
493 | * value: {@link #ALWAYS_ORIGINAL_YES}, {@link #ALWAYS_ORIGINAL_NO}
494 | * Silver Star or Hath Perk: Source Nexus Required 495 | */ 496 | public string alwaysOriginal = ALWAYS_ORIGINAL_NO; 497 | 498 | /** 499 | * Enable the multi-Page Viewer
500 | * key: {@link #KEY_MULTI_PAGE}
501 | * value: {@link #MULTI_PAGE_YES}, {@link #MULTI_PAGE_NO}
502 | * Gold Star or Hath Perk: Multi-Page Viewer Required 503 | */ 504 | public string multiPage = MULTI_PAGE_NO; 505 | 506 | /** 507 | * Multi-Page Viewer Display Style
508 | * key: {@link #KEY_MULTI_PAGE_STYLE}
509 | * value: {@link #MULTI_PAGE_STYLE_N}, {@link #MULTI_PAGE_STYLE_C}, 510 | * {@link #MULTI_PAGE_STYLE_Y}
511 | * Gold Star or Hath Perk: Multi-Page Viewer Required 512 | */ 513 | public string multiPageStyle = MULTI_PAGE_STYLE_N; 514 | 515 | /** 516 | * Multi-Page Viewer Thumbnail Pane
517 | * key: {@link #KEY_MULTI_PAGE_THUMB}
518 | * value: {@link #MULTI_PAGE_THUMB_SHOW}, {@link #MULTI_PAGE_THUMB_HIDE}
519 | * Gold Star or Hath Perk: Multi-Page Viewer Required 520 | */ 521 | public string multiPageThumb = MULTI_PAGE_THUMB_SHOW; 522 | 523 | /** 524 | * Lofi resolution 525 | * key: {@link #KEY_LOFI_RESOLUTION}
526 | * value: {@link #LOFI_RESOLUTION_460X}, {@link #LOFI_RESOLUTION_780X}, 527 | * {@link #LOFI_RESOLUTION_980X} 528 | */ 529 | public string lofiResolution = LOFI_RESOLUTION_980X; 530 | 531 | /** 532 | * Show content warning 533 | * key: {@link #KEY_CONTENT_WARNING}
534 | * value: {@link #CONTENT_WARNING_SHOW}, {@link #CONTENT_WARNING_NOT_SHOW} 535 | */ 536 | public string contentWarning = CONTENT_WARNING_NOT_SHOW; 537 | } 538 | } 539 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/EhRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using static EHentaiAPI.Client.EhClient; 7 | 8 | namespace EHentaiAPI.Client 9 | { 10 | public class EhRequest 11 | { 12 | private EhTask task; 13 | public EhRequestCallback Callback { get; set; } 14 | public EhConfig EhConfig { get; set; } 15 | public object[] Args { get; set; } 16 | private bool mCancel = false; 17 | 18 | public Method Method { get; set; } 19 | 20 | public EhRequest SetMethod(Method method) 21 | { 22 | Method = method; 23 | return this; 24 | } 25 | 26 | public EhRequest SetArgs(params object[] args) 27 | { 28 | Args = args; 29 | return this; 30 | } 31 | 32 | public EhRequest SetCallback(EhRequestCallback callback) 33 | { 34 | Callback = callback; 35 | return this; 36 | } 37 | 38 | public EhRequest SetEhConfig(EhConfig ehConfig) 39 | { 40 | EhConfig = ehConfig; 41 | return this; 42 | } 43 | 44 | public EhRequest SetTask(EhTask task) 45 | { 46 | this.task = task; 47 | return this; 48 | } 49 | 50 | public void Cancel() 51 | { 52 | if (!mCancel) 53 | { 54 | mCancel = true; 55 | if (task != null) 56 | { 57 | task.Stop(); 58 | task = null; 59 | } 60 | } 61 | } 62 | 63 | public bool IsCancelled => mCancel; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/EhRequestBuilder.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.Net.Http; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace EHentaiAPI.Client 12 | { 13 | public class EhRequestBuilder 14 | { 15 | private string url; 16 | private Dictionary headerMap = new Dictionary(); 17 | 18 | private string method = "GET"; 19 | private CookieContainer cookie; 20 | private HttpContent content; 21 | 22 | public EhRequestBuilder(string url) 23 | { 24 | this.url = url; 25 | } 26 | 27 | public EhRequestBuilder(string url, string referer) : this(url, referer, null) 28 | { 29 | 30 | } 31 | 32 | public EhRequestBuilder(string url, string referer, string origin) : this(url) 33 | { 34 | if (!string.IsNullOrWhiteSpace(referer)) 35 | { 36 | addHeader("Referer", referer); 37 | } 38 | if (!string.IsNullOrWhiteSpace(origin)) 39 | { 40 | addHeader("Origin", origin); 41 | } 42 | } 43 | 44 | public void addHeader(string headerName, string value) 45 | { 46 | headerMap[headerName] = value; 47 | } 48 | 49 | public EhRequestBuilder post(HttpContent content) 50 | { 51 | method = "POST"; 52 | this.content = content; 53 | return this; 54 | } 55 | 56 | public IRequest build() 57 | { 58 | var req = Request.Create(url); 59 | req.Method = method.ToUpper(); 60 | 61 | foreach (var pair in headerMap) 62 | { 63 | req.Headers.Add(pair.Key, pair.Value); 64 | } 65 | 66 | if (content != null) 67 | { 68 | req.Content = content; 69 | } 70 | 71 | if (cookie!=null) 72 | { 73 | req.Cookies = cookie; 74 | } 75 | 76 | return req; 77 | } 78 | 79 | public EhRequestBuilder cookies(CookieContainer cookieContainer) 80 | { 81 | cookie = cookieContainer; 82 | return this; 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/EhRequestCallback.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 class EhRequestCallback 10 | { 11 | public Action OnSuccess { get; set; } 12 | public Action OnFailure { get; set; } 13 | public Action OnCancel { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/EhTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics.CodeAnalysis; 4 | using System.Linq; 5 | using System.Net; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using static EHentaiAPI.Client.EhClient; 10 | 11 | namespace EHentaiAPI.Client 12 | { 13 | public class EhTask 14 | { 15 | public Task Task { get; private set; } 16 | 17 | private readonly CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); 18 | private readonly CookieContainer cookieContainer; 19 | 20 | public Method Method { get; private set; } 21 | public EhRequestCallback Callback { get; private set; } 22 | public EhUrl EhUrl { get; private set; } 23 | 24 | public EhTask(Method method, EhUrl ehUrl, EhRequestCallback callback, CookieContainer cookieContainer) 25 | { 26 | Method = method; 27 | Callback = callback; 28 | EhUrl = ehUrl; 29 | this.cookieContainer = cookieContainer; 30 | } 31 | 32 | private async Task DoInBackground(params object[] @params) 33 | { 34 | try 35 | { 36 | switch (Method) 37 | { 38 | case Method.METHOD_SIGN_IN: 39 | return await EhEngine.SignInAsync(cookieContainer, (string)@params[0], (string)@params[1]); 40 | case Method.METHOD_GET_GALLERY_LIST: 41 | return await EhEngine.GetGalleryListAsync(this, cookieContainer, (string)@params[0]); 42 | case Method.METHOD_GET_GALLERY_DETAIL: 43 | return await EhEngine.GetGalleryDetailAsync(this, cookieContainer, (string)@params[0]); 44 | case Method.METHOD_GET_PREVIEW_SET: 45 | return await EhEngine.GetPreviewSetAsync(this, cookieContainer, (string)@params[0]); 46 | case Method.METHOD_GET_RATE_GALLERY: 47 | return await EhEngine.RateGalleryAsync(this, cookieContainer, (long)@params[0], (string)@params[1], (long)@params[2], (string)@params[3], (float)@params[4]); 48 | case Method.METHOD_GET_COMMENT_GALLERY: 49 | return await EhEngine.CommentGalleryAsync(this, cookieContainer, (string)@params[0], (string)@params[1], (long?)@params[2]); 50 | case Method.METHOD_GET_GALLERY_TOKEN: 51 | return await EhEngine.GetGalleryTokenAsync(this, cookieContainer, (long)@params[0], (string)@params[1], (int)@params[2]); 52 | case Method.METHOD_GET_FAVORITES: 53 | return await EhEngine.GetFavoritesAsync(this, cookieContainer, (string)@params[0]); 54 | case Method.METHOD_ADD_FAVORITES: 55 | await EhEngine.AddFavoritesAsync(this, cookieContainer, (long)@params[0], (string)@params[1], (int)@params[2], (string)@params[3]); 56 | break; 57 | case Method.METHOD_ADD_FAVORITES_RANGE: 58 | await EhEngine.AddFavoritesRangeAsync(this, cookieContainer, (long[])@params[0], (string[])@params[1], (int)@params[2]); 59 | break; 60 | case Method.METHOD_MODIFY_FAVORITES: 61 | return await EhEngine.ModifyFavoritesAsync(this, cookieContainer, (string)@params[0], (long[])@params[1], (int)@params[2]); 62 | case Method.METHOD_GET_TORRENT_LIST: 63 | return await EhEngine.GetTorrentListAsync(this, cookieContainer, (string)@params[0], (long)@params[1], (string)@params[2]); 64 | case Method.METHOD_GET_PROFILE: 65 | return await EhEngine.GetProfileAsync(cookieContainer); 66 | case Method.METHOD_VOTE_COMMENT: 67 | return await EhEngine.VoteCommentAsync(this, cookieContainer, (long)@params[0], (string)@params[1], (long)@params[2], (string)@params[3], (long)@params[4], (int)@params[5]); 68 | //case Method.METHOD_IMAGE_SEARCH: 69 | // return EhEngine.imageSearch(this,CookieContainer, (File)@params[0], (Boolean)@params[1], (Boolean)@params[2], (Boolean)@params[3]); 70 | case Method.METHOD_ARCHIVE_LIST: 71 | return await EhEngine.GetArchiveListAsync(this, cookieContainer, (string)@params[0], (long)@params[1], (string)@params[2]); 72 | case Method.METHOD_DOWNLOAD_ARCHIVE: 73 | await EhEngine.DownloadArchiveAsync(this, cookieContainer, (long)@params[0], (string)@params[1], (string)@params[2], (string)@params[3]); 74 | break; 75 | case Method.METHOD_VOTE_TAG: 76 | return await EhEngine.VoteTagAsync(this, cookieContainer, (long)@params[0], (string)@params[1], (long)@params[2], (string)@params[3], (string)@params[4], (int)@params[5]); 77 | case Method.METHOD_GET_GALLERY_PAGE_API: 78 | return await EhEngine.GetGalleryPageApiAsync(this, cookieContainer, (long)@params[0], (int)@params[1], (string)@params[2], (string)@params[3], (string)@params[4]); 79 | case Method.METHOD_GET_GALLERY_PAGE: 80 | return await EhEngine.GetGalleryPageAsync(this, cookieContainer, (string)@params[0], (long)@params[1], (string)@params[2]); 81 | default: 82 | throw new InvalidOperationException("Can't detect method :" + Method); 83 | } 84 | } 85 | catch (Exception e) 86 | { 87 | return e; 88 | } 89 | 90 | return default; 91 | } 92 | 93 | public async void ExecuteOnExecutor(object[] args) 94 | { 95 | Task = DoInBackground(args); 96 | var r = await Task; 97 | if (cancellationTokenSource.IsCancellationRequested) 98 | { 99 | Callback?.OnCancel?.Invoke(); 100 | } 101 | else 102 | { 103 | switch (Task.Status) 104 | { 105 | case TaskStatus.RanToCompletion: 106 | Callback?.OnSuccess?.Invoke(r); 107 | break; 108 | case TaskStatus.Canceled: 109 | Callback?.OnCancel?.Invoke(); 110 | break; 111 | case TaskStatus.Faulted: 112 | Callback?.OnFailure?.Invoke(r as Exception); 113 | break; 114 | default: 115 | break; 116 | } 117 | } 118 | } 119 | 120 | public void Stop() 121 | { 122 | cancellationTokenSource.Cancel(); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/EhUrl.cs: -------------------------------------------------------------------------------- 1 | using EHentaiAPI.Client.Data; 2 | using EHentaiAPI.Utils; 3 | using System; 4 | using System.Linq; 5 | using static EHentaiAPI.Settings; 6 | 7 | namespace EHentaiAPI.Client 8 | { 9 | public class EhUrl 10 | { 11 | public const int SITE_E = 0; 12 | public const int SITE_EX = 1; 13 | 14 | public const string DOMAIN_EX = "exhentai.org"; 15 | public const string DOMAIN_E = "e-hentai.org"; 16 | public const string DOMAIN_LOFI = "lofi.e-hentai.org"; 17 | 18 | public const string HOST_EX = "https://" + DOMAIN_EX + "/"; 19 | public const string HOST_E = "https://" + DOMAIN_E + "/"; 20 | 21 | public const string API_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login&CODE=01"; 22 | 23 | public const string API_E = HOST_E + "api.php"; 24 | public const string API_EX = HOST_EX + "api.php"; 25 | 26 | public const string URL_POPULAR_E = "https://e-hentai.org/popular"; 27 | public const string URL_POPULAR_EX = "https://exhentai.org/popular"; 28 | 29 | public const string URL_IMAGE_SEARCH_E = "https://upload.e-hentai.org/image_lookup.php"; 30 | public const string URL_IMAGE_SEARCH_EX = "https://exhentai.org/upload/image_lookup.php"; 31 | 32 | public const string URL_SIGN_IN = "https://forums.e-hentai.org/index.php?act=Login"; 33 | public const string URL_REGISTER = "https://forums.e-hentai.org/index.php?act=Reg&CODE=00"; 34 | public const string URL_FAVORITES_E = HOST_E + "favorites.php"; 35 | public const string URL_FAVORITES_EX = HOST_EX + "favorites.php"; 36 | public const string URL_FORUMS = "https://forums.e-hentai.org/"; 37 | 38 | public const string REFERER_EX = "https://" + DOMAIN_EX; 39 | public const string REFERER_E = "https://" + DOMAIN_E; 40 | 41 | public const string ORIGIN_EX = REFERER_EX; 42 | public const string ORIGIN_E = REFERER_E; 43 | 44 | public const string URL_UCONFIG_E = HOST_E + "uconfig.php"; 45 | public const string URL_UCONFIG_EX = HOST_EX + "uconfig.php"; 46 | 47 | public const string URL_MY_TAGS_E = HOST_E + "mytags"; 48 | public const string URL_MY_TAGS_EX = HOST_EX + "mytags"; 49 | 50 | public const string URL_WATCHED_E = HOST_E + "watched"; 51 | public const string URL_WATCHED_EX = HOST_EX + "watched"; 52 | 53 | private const string URL_PREFIX_THUMB_E = "https://ehgt.org/"; 54 | //private const string URL_PREFIX_THUMB_EX = "https://exhentai.org/t/"; 55 | 56 | private readonly Settings settings; 57 | 58 | public EhUrl(Settings settings) 59 | { 60 | this.settings = settings; 61 | } 62 | 63 | public string GetGalleryDetailUrl(GalleryInfo info) => GetGalleryDetailUrl(info.Gid, info.Token); 64 | 65 | public string GetGalleryDetailUrl(long gid, string token) 66 | { 67 | return GetGalleryDetailUrl(gid, token, 0, false); 68 | } 69 | 70 | public string GetHost() 71 | { 72 | return settings.GallerySite switch 73 | { 74 | GallerySites.SITE_EX => HOST_EX, 75 | _ => HOST_E, 76 | }; 77 | } 78 | 79 | public string GetFavoritesUrl() 80 | { 81 | return settings.GallerySite switch 82 | { 83 | GallerySites.SITE_EX => URL_FAVORITES_EX, 84 | _ => URL_FAVORITES_E, 85 | }; 86 | } 87 | 88 | public string GetApiUrl() 89 | { 90 | return settings.GallerySite switch 91 | { 92 | GallerySites.SITE_EX => API_EX, 93 | _ => API_E, 94 | }; 95 | } 96 | 97 | public string GetReferer() 98 | { 99 | return settings.GallerySite switch 100 | { 101 | GallerySites.SITE_EX => REFERER_EX, 102 | _ => REFERER_E, 103 | }; 104 | } 105 | 106 | public string GetOrigin() 107 | { 108 | return settings.GallerySite switch 109 | { 110 | GallerySites.SITE_EX => ORIGIN_EX, 111 | _ => ORIGIN_E, 112 | }; 113 | } 114 | 115 | public string GetUConfigUrl() 116 | { 117 | return settings.GallerySite switch 118 | { 119 | GallerySites.SITE_EX => URL_UCONFIG_EX, 120 | _ => URL_UCONFIG_E, 121 | }; 122 | } 123 | 124 | public string GetMyTagsUrl() 125 | { 126 | return settings.GallerySite switch 127 | { 128 | GallerySites.SITE_EX => URL_MY_TAGS_EX, 129 | _ => URL_MY_TAGS_E, 130 | }; 131 | } 132 | 133 | public string GetGalleryDetailUrl(long gid, string token, int index, bool allComment) 134 | { 135 | UrlBuilder builder = new UrlBuilder(GetHost() + "g/" + gid + '/' + token + '/'); 136 | if (index != 0) 137 | { 138 | builder.AddQuery("p", index); 139 | } 140 | if (allComment) 141 | { 142 | builder.AddQuery("hc", 1); 143 | } 144 | return builder.Build(); 145 | } 146 | 147 | public string GetGalleryMultiPageViewerUrl(long gid, string token) 148 | { 149 | UrlBuilder builder = new UrlBuilder(GetHost() + "mpv/" + gid + '/' + token + '/'); 150 | return builder.Build(); 151 | } 152 | 153 | public string GetPageUrl(long gid, int index, string pToken) 154 | { 155 | return GetHost() + "s/" + pToken + '/' + gid + '-' + (index + 1); 156 | } 157 | 158 | public string GetAddFavorites(long gid, string token) 159 | { 160 | return GetHost() + "gallerypopups.php?gid=" + gid + "&t=" + token + "&act=addfav"; 161 | } 162 | 163 | public string GetDownloadArchive(long gid, string token, string or) 164 | { 165 | return GetHost() + "archiver.php?gid=" + gid + "&token=" + token + "&or=" + or; 166 | } 167 | 168 | public static string GetTagDefinitionUrl(string tag) 169 | { 170 | return "https://ehwiki.org/wiki/" + tag.Replace(' ', '_'); 171 | } 172 | 173 | public string GetPopularUrl() 174 | { 175 | return settings.GallerySite switch 176 | { 177 | GallerySites.SITE_EX => URL_POPULAR_EX, 178 | _ => URL_POPULAR_E, 179 | }; 180 | } 181 | 182 | public string GetImageSearchUrl() 183 | { 184 | return settings.GallerySite switch 185 | { 186 | GallerySites.SITE_EX => URL_IMAGE_SEARCH_EX, 187 | _ => URL_IMAGE_SEARCH_E, 188 | }; 189 | } 190 | 191 | public string GetWatchedUrl() 192 | { 193 | return settings.GallerySite switch 194 | { 195 | GallerySites.SITE_EX => URL_WATCHED_EX, 196 | _ => URL_WATCHED_E, 197 | }; 198 | } 199 | 200 | public string GetThumbUrlPrefix() 201 | { 202 | return settings.GallerySite switch 203 | { 204 | _ => URL_PREFIX_THUMB_E,//case SITE_E: 205 | }; 206 | } 207 | 208 | public string GetFixedPreviewThumbUrl(string originUrl) 209 | { 210 | var url = new Uri(originUrl); 211 | if (url == null) return originUrl; 212 | var pathSegments = url.Segments.Skip(1).Select(x => x.EndsWith("/") ? x.Substring(0, x.Length - 1) : x).ToArray(); 213 | if (pathSegments.Length < 3) return originUrl; 214 | 215 | var iterator = pathSegments.Reverse().GetEnumerator(); 216 | // The last segments, like 217 | // 317a1a254cd9c3269e71b2aa2671fe8d28c91097-260198-640-480-png_250.jpg 218 | if (!iterator.MoveNext()) return originUrl; 219 | string lastSegment = iterator.Current; 220 | // The second last segments, like 221 | // 7a 222 | if (!iterator.MoveNext()) return originUrl; 223 | string secondLastSegment = iterator.Current; 224 | // The third last segments, like 225 | // 31 226 | if (!iterator.MoveNext()) return originUrl; 227 | string thirdLastSegment = iterator.Current; 228 | // Check path segments 229 | if (lastSegment != null && secondLastSegment != null 230 | && thirdLastSegment != null 231 | && lastSegment.StartsWith(thirdLastSegment) 232 | && lastSegment.StartsWith(thirdLastSegment + secondLastSegment)) 233 | { 234 | return GetThumbUrlPrefix() + thirdLastSegment + "/" + secondLastSegment + "/" + lastSegment; 235 | } 236 | else 237 | { 238 | return originUrl; 239 | } 240 | } 241 | 242 | public Settings GetSettings() 243 | { 244 | return settings; 245 | } 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/EhUtils.cs: -------------------------------------------------------------------------------- 1 | using EHentaiAPI.Client.Data; 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 10 | { 11 | public class EhUtils 12 | { 13 | public const int NONE = -1; // Use it for homepage 14 | public const int UNKNOWN = 0x400; 15 | 16 | public const int ALL_CATEGORY = EhUtils.UNKNOWN - 1; 17 | //DOUJINSHI|MANGA|ARTIST_CG|GAME_CG|WESTERN|NON_H|IMAGE_SET|COSPLAY|ASIAN_PORN|MISC; 18 | 19 | public const uint BG_COLOR_DOUJINSHI = 0xfff44336; 20 | public const uint BG_COLOR_MANGA = 0xffff9800; 21 | public const uint BG_COLOR_ARTIST_CG = 0xfffbc02d; 22 | public const uint BG_COLOR_GAME_CG = 0xff4caf50; 23 | public const uint BG_COLOR_WESTERN = 0xff8bc34a; 24 | public const uint BG_COLOR_NON_H = 0xff2196f3; 25 | public const uint BG_COLOR_IMAGE_SET = 0xff3f51b5; 26 | public const uint BG_COLOR_COSPLAY = 0xff9c27b0; 27 | public const uint BG_COLOR_ASIAN_PORN = 0xff9575cd; 28 | public const uint BG_COLOR_MISC = 0xfff06292; 29 | public const uint BG_COLOR_UNKNOWN = 0x00000000; 30 | 31 | // Remove [XXX], (XXX), {XXX}, ~XXX~ stuff 32 | public static readonly Regex PATTERN_TITLE_PREFIX = new Regex( 33 | "^(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*"); 34 | // Remove [XXX], (XXX), {XXX}, ~XXX~ stuff and something like ch. 1-23 35 | public static readonly Regex PATTERN_TITLE_SUFFIX = new Regex( 36 | "(?:\\s+ch.[\\s\\d-]+)?(?:(?:\\([^\\)]*\\))|(?:\\[[^\\]]*\\])|(?:\\{[^\\}]*\\})|(?:~[^~]*~)|\\s+)*$", 37 | RegexOptions.IgnoreCase); 38 | 39 | private static readonly int[] CATEGORY_VALUES = { 40 | EhConfig.MISC, 41 | EhConfig.DOUJINSHI, 42 | EhConfig.MANGA, 43 | EhConfig.ARTIST_CG, 44 | EhConfig.GAME_CG, 45 | EhConfig.IMAGE_SET, 46 | EhConfig.COSPLAY, 47 | EhConfig.ASIAN_PORN, 48 | EhConfig.NON_H, 49 | EhConfig.WESTERN, 50 | UNKNOWN}; 51 | 52 | private static readonly string[][] CATEGORY_STRINGS = { 53 | new string[]{"misc"}, 54 | new string[]{"doujinshi"}, 55 | new string[] { "manga" }, 56 | new string[] { "artistcg", "Artist CG Sets", "Artist CG" }, 57 | new string[] { "gamecg", "Game CG Sets", "Game CG" }, 58 | new string[] { "imageset", "Image Sets", "Image Set" }, 59 | new string[] { "cosplay" }, 60 | new string[] { "asianporn", "Asian Porn" }, 61 | new string[] { "non-h" }, 62 | new string[] { "western" }, 63 | new string[] { "unknown" } 64 | }; 65 | 66 | public static int GetCategory(string type) 67 | { 68 | int i; 69 | for (i = 0; i < CATEGORY_STRINGS.Length - 1; i++) 70 | { 71 | foreach (string str in CATEGORY_STRINGS[i]) 72 | if (str.Equals(type, StringComparison.InvariantCultureIgnoreCase)) 73 | return CATEGORY_VALUES[i]; 74 | } 75 | 76 | return CATEGORY_VALUES[i]; 77 | } 78 | 79 | public static string GetCategory(int type) 80 | { 81 | int i; 82 | for (i = 0; i < CATEGORY_VALUES.Length - 1; i++) 83 | { 84 | if (CATEGORY_VALUES[i] == type) 85 | break; 86 | } 87 | return CATEGORY_STRINGS[i][0]; 88 | } 89 | 90 | public static uint GetCategoryColor(int category) 91 | { 92 | return category switch 93 | { 94 | EhConfig.DOUJINSHI => BG_COLOR_DOUJINSHI, 95 | EhConfig.MANGA => BG_COLOR_MANGA, 96 | EhConfig.ARTIST_CG => BG_COLOR_ARTIST_CG, 97 | EhConfig.GAME_CG => BG_COLOR_GAME_CG, 98 | EhConfig.WESTERN => BG_COLOR_WESTERN, 99 | EhConfig.NON_H => BG_COLOR_NON_H, 100 | EhConfig.IMAGE_SET => BG_COLOR_IMAGE_SET, 101 | EhConfig.COSPLAY => BG_COLOR_COSPLAY, 102 | EhConfig.ASIAN_PORN => BG_COLOR_ASIAN_PORN, 103 | EhConfig.MISC => BG_COLOR_MISC, 104 | _ => BG_COLOR_UNKNOWN, 105 | }; 106 | } 107 | 108 | public static string ExtractTitle(string title) 109 | { 110 | if (null == title) 111 | { 112 | return null; 113 | } 114 | title = PATTERN_TITLE_PREFIX.Replace(title, "", 1); 115 | title = PATTERN_TITLE_SUFFIX.Replace(title, "", 1); 116 | // Sometimes title is combined by romaji and english translation. 117 | // Only need romaji. 118 | // TODO But not sure every '|' means that 119 | int index = title.IndexOf('|'); 120 | if (index >= 0) 121 | { 122 | title = title.Substring(0, index - 0); 123 | } 124 | if (string.IsNullOrWhiteSpace(title)) 125 | { 126 | return null; 127 | } 128 | else 129 | { 130 | return title; 131 | } 132 | } 133 | 134 | public static string HandleThumbUrlResolution(Settings settings, string url) 135 | { 136 | if (null == url) 137 | { 138 | return null; 139 | } 140 | 141 | string resolution; 142 | switch (settings.ThumbResolution) 143 | { 144 | default: 145 | case 0: // Auto 146 | return url; 147 | case 1: // 250 148 | resolution = "250"; 149 | break; 150 | case 2: // 300 151 | resolution = "300"; 152 | break; 153 | } 154 | 155 | int index1 = url.LastIndexOf('_'); 156 | int index2 = url.LastIndexOf('.'); 157 | if (index1 >= 0 && index2 >= 0 && index1 < index2) 158 | { 159 | return url.Substring(0, index1 + 1) + resolution + url.Substring(index2); 160 | } 161 | else 162 | { 163 | return url; 164 | } 165 | } 166 | } 167 | } 168 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Exceptions/EhException.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.Exceptions 8 | { 9 | public class EhException : Exception 10 | { 11 | public EhException(string message) : base(message) { } 12 | public EhException(string message, Exception cause) : base(message, cause) { } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Exceptions/NoHAtHClientException.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.Exceptions 8 | { 9 | public class NoHAtHClientException : EhException 10 | { 11 | public NoHAtHClientException(string message) : base(message) 12 | { 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Exceptions/OffensiveException.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.Threading.Tasks; 7 | 8 | namespace EHentaiAPI.Client.Exceptions 9 | { 10 | public class OffensiveException : EhException 11 | { 12 | public OffensiveException() : base("OFFENSIVE") 13 | { 14 | 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Exceptions/ParseException.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.Exceptions 8 | { 9 | public class ParseException : EhException 10 | { 11 | public string Body { get; init; } 12 | 13 | public ParseException(string detailMessage, string body) : base(detailMessage) 14 | { 15 | this.Body = body; 16 | } 17 | 18 | public ParseException(string detailMessage, string body, Exception cause) : base(detailMessage, cause) 19 | { 20 | this.Body = body; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Exceptions/PiningException.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.Exceptions 8 | { 9 | public class PiningException : EhException 10 | { 11 | public PiningException() : base("pining for the fjords") 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Exceptions/StatusCodeException.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.Client.Exceptions 9 | { 10 | public class StatusCodeException : Exception 11 | { 12 | public int ResponseCode { get; init; } 13 | 14 | public StatusCodeException(int responseCode) : base(Enum.Parse(responseCode.ToString()).ToString()) 15 | { 16 | ResponseCode = responseCode; 17 | } 18 | 19 | public StatusCodeException(int responseCode, string message) : base(message) 20 | { 21 | ResponseCode = responseCode; 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Parser/ArchiveParser.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 static class ArchiveParser 11 | { 12 | private readonly static Regex PATTERN_FORM = new Regex("
"); 13 | private readonly static Regex PATTERN_ARCHIVE = new Regex("([^<]+)"); 14 | 15 | public static KeyValuePair[]> Parse(string body) 16 | { 17 | var m = PATTERN_FORM.Match(body); 18 | if (!m.Success) 19 | { 20 | return new ("", Array.Empty>()); 21 | } 22 | string paramOr = m.Groups[1].Value; 23 | List> archiveList = new(); 24 | m = PATTERN_ARCHIVE.Match(body); 25 | while (m.Success) 26 | { 27 | string res = ParserUtils.Trim(m.Groups[1].Value); 28 | string name = ParserUtils.Trim(m.Groups[2].Value); 29 | var item = KeyValuePair.Create(res, name); 30 | archiveList.Add(item); 31 | 32 | m = m.NextMatch(); 33 | } 34 | return KeyValuePair.Create(paramOr, archiveList.ToArray()); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Parser/EventPaneParser.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp; 2 | using EHentaiAPI.ExtendFunction; 3 | using EHentaiAPI.Utils; 4 | using EHentaiAPI.Utils.ExtensionMethods; 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 static class EventPaneParser 14 | { 15 | public static string Parse(string body) 16 | { 17 | string @event = null; 18 | try 19 | { 20 | Document d = Document.Parse(body); 21 | var eventpane = d.GetElementById("eventpane"); 22 | if (eventpane != null) 23 | { 24 | @event = eventpane.ToHtml(); 25 | } 26 | } 27 | catch (Exception e) 28 | { 29 | e.PrintStackTrace(); 30 | } 31 | return @event; 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /EHentaiAPI/Client/Parser/FavoritesParser.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp.Dom; 2 | using EHentaiAPI.Client.Data; 3 | using EHentaiAPI.Client.Exceptions; 4 | using EHentaiAPI.Utils; 5 | using EHentaiAPI.Utils.ExtensionMethods; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Diagnostics; 9 | using System.Linq; 10 | using System.Text; 11 | using System.Threading.Tasks; 12 | 13 | namespace EHentaiAPI.Client.Parser 14 | { 15 | public class FavoritesParser 16 | { 17 | public static Result Parse(Settings settings, string body) 18 | { 19 | if (body.Contains("This page requires you to log on.

")) 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://img.shields.io/badge/nuget-EHentaiAPI%20-blue)](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 | --------------------------------------------------------------------------------