├── .deepsource.toml ├── .gitignore ├── LICENSE ├── NHentaiAPI.Tests ├── BaseUnitTest.cs ├── NHentaiAPI.Tests.csproj ├── NHentaiBookUnitTest.cs ├── NHentaiPictureUnitTest.cs └── NHentaiSearchUnitTest.cs ├── NHentaiAPI.sln ├── NHentaiAPI.sln.DotSettings ├── NHentaiAPI ├── JsonConverters │ ├── ImageTypeConverter.cs │ └── UnixTimestampConverter.cs ├── Models │ ├── Books │ │ ├── Book.cs │ │ ├── Image.cs │ │ ├── Images.cs │ │ ├── Page.cs │ │ ├── Tag.cs │ │ └── Title.cs │ ├── Recommends │ │ └── BookRecommend.cs │ └── Searches │ │ ├── SearchResults.cs │ │ └── SortBy.cs ├── NHentaiAPI.csproj └── NHentaiClient.cs ├── README.md └── appveyor.yml /.deepsource.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [[analyzers]] 4 | name = "csharp" 5 | enabled = true -------------------------------------------------------------------------------- /.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 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # MSTest test Results 33 | [Tt]est[Rr]esult*/ 34 | [Bb]uild[Ll]og.* 35 | 36 | # NUNIT 37 | *.VisualState.xml 38 | TestResult.xml 39 | 40 | # Build Results of an ATL Project 41 | [Dd]ebugPS/ 42 | [Rr]eleasePS/ 43 | dlldata.c 44 | 45 | # Benchmark Results 46 | BenchmarkDotNet.Artifacts/ 47 | 48 | # .NET Core 49 | project.lock.json 50 | project.fragment.lock.json 51 | artifacts/ 52 | **/Properties/launchSettings.json 53 | 54 | *_i.c 55 | *_p.c 56 | *_i.h 57 | *.ilk 58 | *.meta 59 | *.obj 60 | *.pch 61 | *.pdb 62 | *.pgc 63 | *.pgd 64 | *.rsp 65 | *.sbr 66 | *.tlb 67 | *.tli 68 | *.tlh 69 | *.tmp 70 | *.tmp_proj 71 | *.log 72 | *.vspscc 73 | *.vssscc 74 | .builds 75 | *.pidb 76 | *.svclog 77 | *.scc 78 | 79 | # Chutzpah Test files 80 | _Chutzpah* 81 | 82 | # Visual C++ cache files 83 | ipch/ 84 | *.aps 85 | *.ncb 86 | *.opendb 87 | *.opensdf 88 | *.sdf 89 | *.cachefile 90 | *.VC.db 91 | *.VC.VC.opendb 92 | 93 | # Visual Studio profiler 94 | *.psess 95 | *.vsp 96 | *.vspx 97 | *.sap 98 | 99 | # TFS 2012 Local Workspace 100 | $tf/ 101 | 102 | # Guidance Automation Toolkit 103 | *.gpState 104 | 105 | # ReSharper is a .NET coding add-in 106 | _ReSharper*/ 107 | *.[Rr]e[Ss]harper 108 | *.DotSettings.user 109 | 110 | # JustCode is a .NET coding add-in 111 | .JustCode 112 | 113 | # TeamCity is a build add-in 114 | _TeamCity* 115 | 116 | # DotCover is a Code Coverage Tool 117 | *.dotCover 118 | 119 | # Visual Studio code coverage results 120 | *.coverage 121 | *.coveragexml 122 | 123 | # NCrunch 124 | _NCrunch_* 125 | .*crunch*.local.xml 126 | nCrunchTemp_* 127 | 128 | # MightyMoose 129 | *.mm.* 130 | AutoTest.Net/ 131 | 132 | # Web workbench (sass) 133 | .sass-cache/ 134 | 135 | # Installshield output folder 136 | [Ee]xpress/ 137 | 138 | # DocProject is a documentation generator add-in 139 | DocProject/buildhelp/ 140 | DocProject/Help/*.HxT 141 | DocProject/Help/*.HxC 142 | DocProject/Help/*.hhc 143 | DocProject/Help/*.hhk 144 | DocProject/Help/*.hhp 145 | DocProject/Help/Html2 146 | DocProject/Help/html 147 | 148 | # Click-Once directory 149 | publish/ 150 | 151 | # Publish Web Output 152 | *.[Pp]ublish.xml 153 | *.azurePubxml 154 | # TODO: Comment the next line if you want to checkin your web deploy settings 155 | # but database connection strings (with potential passwords) will be unencrypted 156 | *.pubxml 157 | *.publishproj 158 | 159 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 160 | # checkin your Azure Web App publish settings, but sensitive information contained 161 | # in these scripts will be unencrypted 162 | PublishScripts/ 163 | 164 | # NuGet Packages 165 | *.nupkg 166 | # The packages folder can be ignored because of Package Restore 167 | **/packages/* 168 | # except build/, which is used as an MSBuild target. 169 | !**/packages/build/ 170 | # Uncomment if necessary however generally it will be regenerated when needed 171 | #!**/packages/repositories.config 172 | # NuGet v3's project.json files produces more ignorable files 173 | *.nuget.props 174 | *.nuget.targets 175 | 176 | # Microsoft Azure Build Output 177 | csx/ 178 | *.build.csdef 179 | 180 | # Microsoft Azure Emulator 181 | ecf/ 182 | rcf/ 183 | 184 | # Windows Store app package directories and files 185 | AppPackages/ 186 | BundleArtifacts/ 187 | Package.StoreAssociation.xml 188 | _pkginfo.txt 189 | *.appx 190 | 191 | # Visual Studio cache files 192 | # files ending in .cache can be ignored 193 | *.[Cc]ache 194 | # but keep track of directories ending in .cache 195 | !*.[Cc]ache/ 196 | 197 | # Others 198 | ClientBin/ 199 | ~$* 200 | *~ 201 | *.dbmdl 202 | *.dbproj.schemaview 203 | *.jfm 204 | *.pfx 205 | *.publishsettings 206 | orleans.codegen.cs 207 | 208 | # Since there are multiple workflows, uncomment next line to ignore bower_components 209 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 210 | #bower_components/ 211 | 212 | # RIA/Silverlight projects 213 | Generated_Code/ 214 | 215 | # Backup & report files from converting an old project file 216 | # to a newer Visual Studio version. Backup files are not needed, 217 | # because we have git ;-) 218 | _UpgradeReport_Files/ 219 | Backup*/ 220 | UpgradeLog*.XML 221 | UpgradeLog*.htm 222 | 223 | # SQL Server files 224 | *.mdf 225 | *.ldf 226 | *.ndf 227 | 228 | # Business Intelligence projects 229 | *.rdl.data 230 | *.bim.layout 231 | *.bim_*.settings 232 | 233 | # Microsoft Fakes 234 | FakesAssemblies/ 235 | 236 | # GhostDoc plugin setting file 237 | *.GhostDoc.xml 238 | 239 | # Node.js Tools for Visual Studio 240 | .ntvs_analysis.dat 241 | node_modules/ 242 | 243 | # Typescript v1 declaration files 244 | typings/ 245 | 246 | # Visual Studio 6 build log 247 | *.plg 248 | 249 | # Visual Studio 6 workspace options file 250 | *.opt 251 | 252 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 253 | *.vbw 254 | 255 | # Visual Studio LightSwitch build output 256 | **/*.HTMLClient/GeneratedArtifacts 257 | **/*.DesktopClient/GeneratedArtifacts 258 | **/*.DesktopClient/ModelManifest.xml 259 | **/*.Server/GeneratedArtifacts 260 | **/*.Server/ModelManifest.xml 261 | _Pvt_Extensions 262 | 263 | # Paket dependency manager 264 | .paket/paket.exe 265 | paket-files/ 266 | 267 | # FAKE - F# Make 268 | .fake/ 269 | 270 | # JetBrains Rider 271 | .idea/ 272 | *.sln.iml 273 | 274 | # CodeRush 275 | .cr/ 276 | 277 | # Python Tools for Visual Studio (PTVS) 278 | __pycache__/ 279 | *.pyc 280 | 281 | # Cake - Uncomment if you are using it 282 | # tools/** 283 | # !tools/packages.config 284 | 285 | # Tabs Studio 286 | *.tss 287 | 288 | # Telerik's JustMock configuration file 289 | *.jmconfig 290 | 291 | # BizTalk build output 292 | *.btp.cs 293 | *.btm.cs 294 | *.odx.cs 295 | *.xsd.cs 296 | 297 | # Test game 298 | TestGames/ 299 | osu.GameBoy.Tests/Resources/ROM/Tetris.gb 300 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 SylveonDeko 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 | -------------------------------------------------------------------------------- /NHentaiAPI.Tests/BaseUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace NHentaiAPI.Tests; 5 | 6 | public class BaseUnitTest 7 | { 8 | protected NHentaiClient NHentaiClient { get; private set; } 9 | 10 | [TestInitialize] 11 | public void InitializeTest() 12 | { 13 | NHentaiClient = new TestNHentaiClient("a", new Dictionary()); 14 | } 15 | } 16 | 17 | public class TestNHentaiClient : NHentaiClient 18 | { 19 | public TestNHentaiClient(string userAgent, Dictionary cookies = null) : base(userAgent, cookies) 20 | { 21 | } 22 | 23 | #region Urls 24 | 25 | //protected override string ApiRootUrl => "https://nhent.ai"; 26 | 27 | #endregion 28 | } -------------------------------------------------------------------------------- /NHentaiAPI.Tests/NHentaiAPI.Tests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net8.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /NHentaiAPI.Tests/NHentaiBookUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace NHentaiAPI.Tests; 6 | 7 | /// 8 | /// see : 9 | /// https://github.com/andy840119/NHentaiSharp/blob/master/NHentaiSharp.UnitTests/Program.cs 10 | /// 11 | [TestClass] 12 | public class NHentaiBookUnitTest : BaseUnitTest 13 | { 14 | /// 15 | /// Get book detail 16 | /// https://nhentai.net/g/161194/ 17 | /// 18 | /// 19 | [TestMethod] 20 | public async Task TestBookResult() 21 | { 22 | // https://nhentai.net/api/gallery/161194 23 | var result = await NHentaiClient.GetBookAsync(161194); 24 | 25 | Assert.AreEqual("[ユイザキカズヤ] つなかん。 (COMIC ポプリクラブ 2013年8月号) [英訳]", result.Title.Japanese); 26 | Assert.AreEqual("Tsuna-kan. | Tuna Can", result.Title.Pretty); 27 | Assert.AreEqual("[Yuizaki Kazuya] Tsuna-kan. | Tuna Can (COMIC Potpourri Club 2013-08) [English] [PSYN]", 28 | result.Title.English); 29 | Assert.AreEqual("160413", result.UploadDate.ToString("yyMMdd")); 30 | Assert.AreEqual(true, result.Tags.Any(x => x.Id == 19440)); 31 | Assert.AreEqual(17, result.NumPages); 32 | Assert.AreEqual(17, result.Images.Pages.Count); 33 | Assert.AreEqual(161194, result.Id); 34 | } 35 | 36 | /// 37 | /// Get recommend book 38 | /// https://nhentai.net/g/161194/ 39 | /// 40 | /// 41 | [TestMethod] 42 | public async Task TestBookRecommendResult() 43 | { 44 | // https://nhentai.net/api/gallery/161194/related 45 | var result = await NHentaiClient.GetBookRecommendAsync(161194); 46 | 47 | // At least one recommend 48 | Assert.AreEqual(true, result.Result.Any()); 49 | } 50 | } -------------------------------------------------------------------------------- /NHentaiAPI.Tests/NHentaiPictureUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | 4 | namespace NHentaiAPI.Tests; 5 | 6 | [TestClass] 7 | public class NHentaiPictureUnitTest : BaseUnitTest 8 | { 9 | /// 10 | /// Get picture by book's media id and pageNumber 11 | /// https://nhentai.net/g/123/ 12 | /// 13 | /// 14 | [TestMethod] 15 | public async Task TestGetPictureResult() 16 | { 17 | // Get book no 123 18 | var book = await NHentaiClient.GetBookAsync(123); 19 | 20 | // Check this book is right media number 21 | Assert.AreEqual(635, book.MediaId); 22 | 23 | // Check url 24 | var imageUrl = NHentaiClient.GetPictureUrl(book, 1); 25 | Assert.AreEqual(imageUrl, "https://i.nhentai.net/galleries/635/1.jpg"); 26 | 27 | // Make sure image is downloaded 28 | var result = await NHentaiClient.GetPictureAsync(book, 1); 29 | Assert.AreEqual(true, result.Length > 0); 30 | } 31 | 32 | /// 33 | /// Get picture by book's media id and pageNumber 34 | /// https://nhentai.net/g/288869 / 35 | /// 36 | /// 37 | [TestMethod] 38 | public async Task TestGetGifPictureResult() 39 | { 40 | // Get book no 288869 41 | var book = await NHentaiClient.GetBookAsync(288869); 42 | 43 | // Check this book is right media number 44 | Assert.AreEqual(1504878, book.MediaId); 45 | 46 | // Check url 47 | var imageUrl = NHentaiClient.GetPictureUrl(book, 22); 48 | Assert.AreEqual(imageUrl, "https://i.nhentai.net/galleries/1504878/22.gif"); 49 | 50 | // Make sure image is downloaded 51 | var result = await NHentaiClient.GetPictureAsync(book, 22); 52 | Assert.AreEqual(true, result.Length > 0); 53 | } 54 | 55 | /// 56 | /// Get thumbnail by book's media id and pageNumber 57 | /// https://nhentai.net/g/123/ 58 | /// 59 | /// 60 | [TestMethod] 61 | public async Task TestGetThumbPictureResult() 62 | { 63 | // Get book no 123 64 | var book = await NHentaiClient.GetBookAsync(123); 65 | 66 | // Check this book is right media number 67 | Assert.AreEqual(635, book.MediaId); 68 | 69 | // Check url 70 | var imageUrl = NHentaiClient.GetThumbPictureUrl(book, 1); 71 | Assert.AreEqual(imageUrl, "https://t.nhentai.net/galleries/635/1t.jpg"); 72 | 73 | // Make sure image is downloaded 74 | var result = await NHentaiClient.GetThumbPictureAsync(book, 1); 75 | Assert.AreEqual(true, result.Length > 0); 76 | } 77 | 78 | /// 79 | /// Get big cover by book's media id 80 | /// https://nhentai.net/g/123/ 81 | /// 82 | /// 83 | [TestMethod] 84 | public async Task TestGetBigCoverPictureResult() 85 | { 86 | // Get book no 123 87 | var book = await NHentaiClient.GetBookAsync(123); 88 | 89 | // Check this book is right media number 90 | Assert.AreEqual(635, book.MediaId); 91 | 92 | // Check url 93 | var imageUrl = NHentaiClient.GetBigCoverUrl(book); 94 | Assert.AreEqual(imageUrl, "https://t.nhentai.net/galleries/635/cover.jpg"); 95 | 96 | // Make sure image is downloaded 97 | var result = await NHentaiClient.GetBigCoverPictureAsync(book); 98 | Assert.AreEqual(true, result.Length > 0); 99 | } 100 | 101 | /// 102 | /// Get origin picture by book's media id and pageNumber 103 | /// https://nhentai.net/g/123/ 104 | /// 105 | /// 106 | [TestMethod] 107 | public async Task TestGetOriginPictureResult() 108 | { 109 | // Get book no 123 110 | var book = await NHentaiClient.GetBookAsync(123); 111 | 112 | // Check this book is right media number 113 | Assert.AreEqual(635, book.MediaId); 114 | 115 | // Check url 116 | var imageUrl = NHentaiClient.GetOriginPictureUrl(book, 1); 117 | Assert.AreEqual(imageUrl, "https://i.nhentai.net/galleries/635/1.jpg"); 118 | 119 | // Make sure image is downloaded 120 | var result = await NHentaiClient.GetOriginPictureAsync(book, 1); 121 | Assert.AreEqual(true, result.Length > 0); 122 | } 123 | 124 | /// 125 | /// Get thumbnail cover by book's media id 126 | /// https://nhentai.net/g/123/ 127 | /// 128 | /// 129 | [TestMethod] 130 | public async Task TestBookThumbPictureResult() 131 | { 132 | // Get book no 123 133 | var book = await NHentaiClient.GetBookAsync(123); 134 | 135 | // Check this book is right media number 136 | Assert.AreEqual(635, book.MediaId); 137 | 138 | // Check url 139 | var imageUrl = NHentaiClient.GetBookThumbUrl(book); 140 | Assert.AreEqual(imageUrl, "https://t.nhentai.net/galleries/635/thumb.jpg"); 141 | 142 | // Make sure image is downloaded 143 | var result = await NHentaiClient.GetBookThumbPictureAsync(book); 144 | Assert.AreEqual(true, result.Length > 0); 145 | } 146 | } -------------------------------------------------------------------------------- /NHentaiAPI.Tests/NHentaiSearchUnitTest.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading.Tasks; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using NHentaiAPI.Models.Books; 5 | using NHentaiAPI.Models.Searches; 6 | 7 | namespace NHentaiAPI.Tests; 8 | 9 | /// 10 | /// see : 11 | /// https://github.com/NHMoeDev/NHentai-android/issues/27 12 | /// 13 | [TestClass] 14 | public class NHentaiSearchUnitTest : BaseUnitTest 15 | { 16 | /// 17 | /// Target number of record in single page 18 | /// 19 | protected virtual int ResultNumber => 25; 20 | 21 | /// 22 | /// Get home page search result 23 | /// https://nhentai.net/galleries/all?page=1 24 | /// 25 | /// 26 | [TestMethod] 27 | public async Task TestSearchHomePageResult() 28 | { 29 | // https://nhentai.net/api/galleries/all?page=1 30 | var result = await NHentaiClient.GetHomePageListAsync(1); 31 | 32 | Assert.AreEqual(ResultNumber, result.PerPage); 33 | Assert.AreEqual(ResultNumber, result.Result.Count); 34 | } 35 | 36 | /// 37 | /// Get search result by keyword 38 | /// https://nhentai.net/galleries/search?query=school%20swimsuit%20loli%20full%20color&page=2 39 | /// 40 | /// 41 | [TestMethod] 42 | public async Task TestSearchResult() 43 | { 44 | // https://nhentai.net/api/galleries/search?query=school 45 | var result = await NHentaiClient.GetSearchPageListAsync("school", 1); 46 | 47 | Assert.AreEqual(ResultNumber, result.PerPage); 48 | Assert.AreEqual(ResultNumber, result.Result.Count); 49 | } 50 | 51 | /// 52 | /// Get search result by tag, can be sort by popular 53 | /// https://nhentai.net/galleries/tagged?tag_id=1&page=1&sort=popular 54 | /// 55 | /// 56 | [TestMethod] 57 | public async Task TestTagResult() 58 | { 59 | // https://nhentai.net/api/galleries/tagged?tag_id=1&page=1&sort=popular 60 | var tag = new Tag 61 | { 62 | Id = 1 63 | }; 64 | var result = await NHentaiClient.GetTagPageListAsync(tag, SortBy.Popular, 1); 65 | 66 | Assert.AreEqual(ResultNumber, result.PerPage); 67 | Assert.AreEqual(true, result.Result.Any()); 68 | } 69 | } -------------------------------------------------------------------------------- /NHentaiAPI.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.28307.136 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NHentaiAPI", "NHentaiAPI\NHentaiAPI.csproj", "{F25AFF9C-12CD-4BFE-A3E7-0D3BEC3B0B28}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NHentaiAPI.Tests", "NHentaiAPI.Tests\NHentaiAPI.Tests.csproj", "{8C6AA38D-AA5B-4842-AAD5-5E0BE4FADE39}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {F25AFF9C-12CD-4BFE-A3E7-0D3BEC3B0B28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {F25AFF9C-12CD-4BFE-A3E7-0D3BEC3B0B28}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {F25AFF9C-12CD-4BFE-A3E7-0D3BEC3B0B28}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {F25AFF9C-12CD-4BFE-A3E7-0D3BEC3B0B28}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {8C6AA38D-AA5B-4842-AAD5-5E0BE4FADE39}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {8C6AA38D-AA5B-4842-AAD5-5E0BE4FADE39}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {8C6AA38D-AA5B-4842-AAD5-5E0BE4FADE39}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {8C6AA38D-AA5B-4842-AAD5-5E0BE4FADE39}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {71420EA2-7A38-4BD5-922C-A2A0E441AF50} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /NHentaiAPI.sln.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | True 3 | True 4 | True 5 | True 6 | True 7 | True -------------------------------------------------------------------------------- /NHentaiAPI/JsonConverters/ImageTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | using NHentaiAPI.Models.Books; 4 | 5 | namespace NHentaiAPI.JsonConverters; 6 | 7 | /// 8 | /// Converts between single-character string representations and ImageType enum values 9 | /// 10 | public class ImageTypeConverter : JsonConverter 11 | { 12 | /// 13 | /// Reads a JSON string value and converts it to an ImageType enum value 14 | /// 15 | /// The Utf8JsonReader to read from 16 | /// The type to convert to 17 | /// The JsonSerializerOptions to use 18 | /// An ImageType enum value corresponding to the JSON string 19 | public override ImageType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 20 | { 21 | var value = reader.GetString(); 22 | return value?.ToLower() switch 23 | { 24 | "j" => ImageType.Jpg, 25 | "p" => ImageType.Png, 26 | "g" => ImageType.Gif, 27 | _ => ImageType.Jpg // Default to JPG if unknown 28 | }; 29 | } 30 | 31 | /// 32 | /// Writes an ImageType enum value as a single-character JSON string 33 | /// 34 | /// The JsonWriter to write to 35 | /// The ImageType value to convert 36 | /// The JsonSerializerOptions to use 37 | public override void Write(Utf8JsonWriter writer, ImageType value, JsonSerializerOptions options) 38 | { 39 | var stringValue = value switch 40 | { 41 | ImageType.Jpg => "j", 42 | ImageType.Png => "p", 43 | ImageType.Gif => "g", 44 | _ => "j" 45 | }; 46 | writer.WriteStringValue(stringValue); 47 | } 48 | } -------------------------------------------------------------------------------- /NHentaiAPI/JsonConverters/UnixTimestampConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace NHentaiAPI.JsonConverters; 5 | 6 | /// 7 | /// Converts between Unix timestamps and DateTime values during JSON serialization/deserialization 8 | /// 9 | public class UnixTimestampConverter : JsonConverter 10 | { 11 | /// 12 | /// Converts a Unix timestamp from JSON to a DateTime value 13 | /// 14 | /// The JSON reader 15 | /// The type to convert to (DateTime) 16 | /// Serialization options 17 | /// A DateTime converted from the Unix timestamp 18 | public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 19 | { 20 | // Read the Unix timestamp (seconds since epoch) 21 | var unixTime = reader.GetInt64(); 22 | 23 | // Convert Unix timestamp to DateTime 24 | // Unix epoch starts from January 1, 1970 25 | return DateTimeOffset.FromUnixTimeSeconds(unixTime).DateTime; 26 | } 27 | 28 | /// 29 | /// Converts a DateTime value to a Unix timestamp for JSON 30 | /// 31 | /// The JSON writer 32 | /// The DateTime value to convert 33 | /// Serialization options 34 | public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options) 35 | { 36 | // Convert DateTime to Unix timestamp 37 | var unixTime = new DateTimeOffset(value).ToUnixTimeSeconds(); 38 | writer.WriteNumberValue(unixTime); 39 | } 40 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Books/Book.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using NHentaiAPI.JsonConverters; 3 | 4 | namespace NHentaiAPI.Models.Books; 5 | 6 | /// 7 | /// Represents a complete book with all its metadata 8 | /// 9 | public class Book 10 | { 11 | /// 12 | /// Gets or sets the unique identifier for the book 13 | /// 14 | [JsonPropertyName("id")] 15 | public int Id { get; set; } 16 | 17 | /// 18 | /// Gets or sets the media identifier for the book 19 | /// 20 | [JsonPropertyName("media_id")] 21 | public int MediaId { get; set; } 22 | 23 | /// 24 | /// Gets or sets the titles of the book in different languages 25 | /// 26 | [JsonPropertyName("title")] 27 | public Title Title { get; set; } 28 | 29 | /// 30 | /// Gets or sets the collection of images associated with the book 31 | /// 32 | [JsonPropertyName("images")] 33 | public Images Images { get; set; } 34 | 35 | /// 36 | /// Gets or sets the scanlator/translator of the book 37 | /// 38 | [JsonPropertyName("scanlator")] 39 | public string Scanlator { get; set; } 40 | 41 | /// 42 | /// Gets or sets the date when the book was uploaded 43 | /// 44 | [JsonPropertyName("upload_date")] 45 | [JsonConverter(typeof(UnixTimestampConverter))] 46 | public DateTime UploadDate { get; set; } 47 | 48 | /// 49 | /// Gets or sets the list of tags associated with the book 50 | /// 51 | [JsonPropertyName("tags")] 52 | public List Tags { get; set; } 53 | 54 | /// 55 | /// Gets or sets the total number of pages in the book 56 | /// 57 | [JsonPropertyName("num_pages")] 58 | public int NumPages { get; set; } 59 | 60 | /// 61 | /// Gets or sets the number of times the book has been favorited 62 | /// 63 | [JsonPropertyName("num_favorites")] 64 | public int NumFavorites { get; set; } 65 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Books/Image.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.Serialization; 2 | using System.Text.Json.Serialization; 3 | using NHentaiAPI.JsonConverters; 4 | 5 | namespace NHentaiAPI.Models.Books; 6 | 7 | /// 8 | /// Represents an image's metadata 9 | /// 10 | public class Image 11 | { 12 | /// 13 | /// Gets or sets the type/format of the image 14 | /// 15 | [JsonPropertyName("t")] 16 | [JsonConverter(typeof(ImageTypeConverter))] 17 | public ImageType Type { get; set; } 18 | 19 | /// 20 | /// Gets or sets the width of the image in pixels 21 | /// 22 | [JsonPropertyName("w")] 23 | public int Width { get; set; } 24 | 25 | /// 26 | /// Gets or sets the height of the image in pixels 27 | /// 28 | [JsonPropertyName("h")] 29 | public int Height { get; set; } 30 | } 31 | 32 | /// 33 | /// Defines the supported image formats 34 | /// 35 | public enum ImageType 36 | { 37 | /// 38 | /// JPEG image format 39 | /// 40 | [EnumMember(Value = "j")] Jpg, 41 | 42 | /// 43 | /// PNG image format 44 | /// 45 | [EnumMember(Value = "p")] Png, 46 | 47 | /// 48 | /// GIF image format 49 | /// 50 | [EnumMember(Value = "g")] Gif 51 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Books/Images.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace NHentaiAPI.Models.Books; 4 | 5 | /// 6 | /// Contains all images associated with a book 7 | /// 8 | public class Images 9 | { 10 | /// 11 | /// Gets or sets the list of page images 12 | /// 13 | [JsonPropertyName("pages")] 14 | public List Pages { get; set; } 15 | 16 | /// 17 | /// Gets or sets the cover image 18 | /// 19 | [JsonPropertyName("cover")] 20 | public Image Cover { get; set; } 21 | 22 | /// 23 | /// Gets or sets the thumbnail image 24 | /// 25 | [JsonPropertyName("thumbnail")] 26 | public Image Thumbnail { get; set; } 27 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Books/Page.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace NHentaiAPI.Models.Books; 4 | 5 | /// 6 | /// Represents a page's basic information 7 | /// 8 | public class Page 9 | { 10 | /// 11 | /// Gets or sets the type identifier of the page 12 | /// 13 | [JsonPropertyName("t")] 14 | public string T { get; set; } 15 | 16 | /// 17 | /// Gets or sets the width of the page image 18 | /// 19 | [JsonPropertyName("w")] 20 | public int W { get; set; } 21 | 22 | /// 23 | /// Gets or sets the height of the page image 24 | /// 25 | [JsonPropertyName("h")] 26 | public int H { get; set; } 27 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Books/Tag.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace NHentaiAPI.Models.Books; 4 | 5 | /// 6 | /// Represents a tag used to categorize books 7 | /// 8 | public class Tag 9 | { 10 | /// 11 | /// Gets or sets the unique identifier for the tag 12 | /// 13 | [JsonPropertyName("id")] 14 | public int Id { get; set; } 15 | 16 | /// 17 | /// Gets or sets the type of the tag 18 | /// 19 | [JsonPropertyName("type")] 20 | public string Type { get; set; } 21 | 22 | /// 23 | /// Gets or sets the name of the tag 24 | /// 25 | [JsonPropertyName("name")] 26 | public string Name { get; set; } 27 | 28 | /// 29 | /// Gets or sets the URL for the tag's page 30 | /// 31 | [JsonPropertyName("url")] 32 | public string Url { get; set; } 33 | 34 | /// 35 | /// Gets or sets the number of books with this tag 36 | /// 37 | [JsonPropertyName("count")] 38 | public int Count { get; set; } 39 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Books/Title.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace NHentaiAPI.Models.Books; 4 | 5 | /// 6 | /// Represents a book's title in different languages 7 | /// 8 | public class Title 9 | { 10 | /// 11 | /// Gets or sets the English title 12 | /// 13 | [JsonPropertyName("english")] 14 | public string English { get; set; } 15 | 16 | /// 17 | /// Gets or sets the Japanese title 18 | /// 19 | [JsonPropertyName("japanese")] 20 | public string Japanese { get; set; } 21 | 22 | /// 23 | /// Gets or sets the formatted/pretty title 24 | /// 25 | [JsonPropertyName("pretty")] 26 | public string Pretty { get; set; } 27 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Recommends/BookRecommend.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using NHentaiAPI.Models.Books; 3 | 4 | namespace NHentaiAPI.Models.Recommends; 5 | 6 | /// 7 | /// Represents a list of recommended books 8 | /// 9 | public class BookRecommend 10 | { 11 | /// 12 | /// Gets or sets the list of recommended books 13 | /// 14 | [JsonPropertyName("result")] 15 | public List Result { get; set; } 16 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Searches/SearchResults.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | using NHentaiAPI.Models.Books; 3 | 4 | namespace NHentaiAPI.Models.Searches; 5 | 6 | /// 7 | /// Represents search results containing a list of books and pagination information 8 | /// 9 | public class SearchResults 10 | { 11 | /// 12 | /// Gets or sets the list of books returned in the search results 13 | /// 14 | [JsonPropertyName("result")] 15 | public List Result { get; set; } 16 | 17 | /// 18 | /// Gets or sets the total number of pages in the search results 19 | /// 20 | [JsonPropertyName("num_pages")] 21 | public int NumPages { get; set; } 22 | 23 | /// 24 | /// Gets or sets the number of items per page 25 | /// 26 | [JsonPropertyName("per_page")] 27 | public int PerPage { get; set; } 28 | } -------------------------------------------------------------------------------- /NHentaiAPI/Models/Searches/SortBy.cs: -------------------------------------------------------------------------------- 1 | namespace NHentaiAPI.Models.Searches; 2 | 3 | /// 4 | /// Search sort method 5 | /// 6 | public enum SortBy 7 | { 8 | /// 9 | /// Default sorting 10 | /// 11 | Default, 12 | 13 | /// 14 | /// Sorting by popular 15 | /// 16 | Popular 17 | } -------------------------------------------------------------------------------- /NHentaiAPI/NHentaiAPI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | net8.0 4 | enable 5 | enable 6 | latest 7 | 1.8.0 8 | 1.8.0 9 | 1.8.0 10 | 1.8.0 11 | true 12 | SylveonDeko 13 | SylveonDeko 14 | nHentai 15 | A (full?) nHentai API implementation for .NET 16 | README.md 17 | LICENSE 18 | https://github.com/SylveonDeko/NHentaiAPI 19 | https://github.com/SylveonDeko/NHentaiAPI.git 20 | git 21 | nhentai;n-hentai;hentai;api;csharp;dotnet 22 | true 23 | $(NoWarn);CS1591 24 | 25 | [1.8.0] 26 | - Switched to using System.Text.Json instead of Newtonsoft. 27 | - Added proper JSON type handling via converters. 28 | - Updated to .NET 8.0 29 | - Added proper async handling 30 | - Updated thumbnail and image urls to work with nhentais' load balancing urls. 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /NHentaiAPI/NHentaiClient.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | using NHentaiAPI.Models.Books; 5 | using NHentaiAPI.Models.Recommends; 6 | using NHentaiAPI.Models.Searches; 7 | 8 | namespace NHentaiAPI; 9 | 10 | /// 11 | /// Client for interacting with the n-Hentai API 12 | /// 13 | public class NHentaiClient : IDisposable 14 | { 15 | /// 16 | /// Releases the resources used by the HTTP client 17 | /// 18 | public void Dispose() 19 | { 20 | _client.Dispose(); 21 | } 22 | 23 | #region Client 24 | 25 | private readonly HttpClient _client; 26 | private readonly Random _rand; 27 | 28 | private readonly JsonSerializerOptions _options = new() 29 | { 30 | NumberHandling = JsonNumberHandling.AllowReadingFromString, 31 | PropertyNameCaseInsensitive = true, 32 | Converters = { new JsonStringEnumConverter() } 33 | }; 34 | 35 | /// 36 | /// Initializes a new instance of the NHentaiClient 37 | /// 38 | /// User agent string to use for requests 39 | /// Optional dictionary of cookies to include with requests 40 | public NHentaiClient(string userAgent, Dictionary? cookies = null) 41 | { 42 | _rand = new Random(); 43 | var cookies1 = new CookieContainer(); 44 | var handler = new HttpClientHandler { CookieContainer = cookies1 }; 45 | _client = new HttpClient(handler); 46 | 47 | _client.DefaultRequestHeaders.Add("User-Agent", userAgent); 48 | 49 | if (cookies == null) return; 50 | var cookieUri = new Uri(ApiRootUrl); 51 | 52 | foreach (var cookie in cookies) cookies1.Add(cookieUri, new Cookie(cookie.Key, cookie.Value)); 53 | } 54 | 55 | #endregion 56 | 57 | #region Urls 58 | 59 | /// 60 | /// Gets the base API endpoint URL 61 | /// 62 | private string ApiRootUrl => "https://nhentai.net"; 63 | 64 | /// 65 | /// Gets the base URL for full-resolution images 66 | /// 67 | private string ImageRootUrl() 68 | { 69 | var rand = _rand.Next(1, 4); 70 | return $"https://i{rand}.nhentai.net"; 71 | } 72 | 73 | /// 74 | /// Gets the base URL for thumbnail images 75 | /// 76 | private string ThumbnailRootUrl() 77 | { 78 | var rand = _rand.Next(1, 4); 79 | return $"https://t{rand}.nhentai.net"; 80 | } 81 | 82 | #endregion 83 | 84 | #region Data urls 85 | 86 | /// 87 | /// Constructs the URL for retrieving the homepage content 88 | /// 89 | /// Page number to retrieve 90 | /// URL for the specified homepage 91 | private string GetHomePageUrl(int pageNum) 92 | { 93 | return $"{ApiRootUrl}/api/galleries/all?page={pageNum}"; 94 | } 95 | 96 | /// 97 | /// Constructs the URL for performing a search 98 | /// 99 | /// Search query text 100 | /// Page number of results 101 | /// URL for the search with specified parameters 102 | private string GetSearchUrl(string content, int pageNum) 103 | { 104 | return $"{ApiRootUrl}/api/galleries/search?" + 105 | $"query={content.Replace(" ", "+")}&" + 106 | $"page={pageNum}"; 107 | } 108 | 109 | /// 110 | /// Constructs the URL for retrieving content with a specific tag 111 | /// 112 | /// Tag to filter by 113 | /// Whether to sort by popularity 114 | /// Page number to retrieve 115 | /// URL for the tag search with specified parameters 116 | private string GetTagUrl(Tag tag, bool isPopularList, int pageNum) 117 | { 118 | return $"{ApiRootUrl}/api/galleries/tagged?" + 119 | $"tag_id={tag.Id}" + 120 | $"&page={pageNum}" + 121 | (isPopularList ? "&sort=popular" : ""); 122 | } 123 | 124 | /// 125 | /// Constructs the URL for retrieving book details 126 | /// 127 | /// ID of the book 128 | /// URL for the specified book's details 129 | private string GetBookDetailsUrl(int bookId) 130 | { 131 | return $"{ApiRootUrl}/api/gallery/{bookId}"; 132 | } 133 | 134 | /// 135 | /// Constructs the URL for retrieving book recommendations 136 | /// 137 | /// ID of the book to get recommendations for 138 | /// URL for recommendations related to the specified book 139 | private string GetBookRecommendUrl(int bookId) 140 | { 141 | return $"{ApiRootUrl}/api/gallery/{bookId}/related"; 142 | } 143 | 144 | /// 145 | /// Constructs the URL for accessing a gallery's images 146 | /// 147 | /// ID of the gallery 148 | /// Base URL for the gallery's full-size images 149 | private string GetGalleryUrl(int galleryId) 150 | { 151 | return $"{ImageRootUrl()}/galleries/{galleryId}"; 152 | } 153 | 154 | /// 155 | /// Constructs the URL for accessing a gallery's thumbnail images 156 | /// 157 | /// ID of the gallery 158 | /// Base URL for the gallery's thumbnail images 159 | private string GetThumbGalleryUrl(int galleryId) 160 | { 161 | return $"{ThumbnailRootUrl()}/galleries/{galleryId}"; 162 | } 163 | 164 | #endregion 165 | 166 | #region Picture urls 167 | 168 | /// 169 | /// Gets the URL for a specific page image in a book 170 | /// 171 | /// Book containing the image 172 | /// Page number to retrieve 173 | /// Full URL for the specified page image 174 | public string GetPictureUrl(Book book, int pageNum) 175 | { 176 | var image = GetImage(book, pageNum); 177 | var fileType = ConvertType(image.Type); 178 | return GetPictureUrl(book.MediaId, pageNum, fileType); 179 | } 180 | 181 | /// 182 | /// Gets the URL for a thumbnail of a specific page in a book 183 | /// 184 | /// Book containing the image 185 | /// Page number to retrieve 186 | /// Full URL for the specified page's thumbnail 187 | public string GetThumbPictureUrl(Book book, int pageNum) 188 | { 189 | var image = GetImage(book, pageNum); 190 | var fileType = ConvertType(image.Type); 191 | return GetThumbPictureUrl(book.MediaId, pageNum, fileType); 192 | } 193 | 194 | /// 195 | /// Gets the URL for a book's large cover image 196 | /// 197 | /// Book to get the cover for 198 | /// Full URL for the book's cover image 199 | public string GetBigCoverUrl(Book book) 200 | { 201 | return GetBigCoverUrl(book.MediaId); 202 | } 203 | 204 | /// 205 | /// Gets the URL for the original full-size version of a page 206 | /// 207 | /// Book containing the image 208 | /// Page number to retrieve 209 | /// Full URL for the original version of the specified page 210 | public string GetOriginPictureUrl(Book book, int pageNum) 211 | { 212 | return GetOriginPictureUrl(book.MediaId, pageNum); 213 | } 214 | 215 | /// 216 | /// Gets the URL for a book's thumbnail image 217 | /// 218 | /// Book to get the thumbnail for 219 | /// Full URL for the book's thumbnail image 220 | public string GetBookThumbUrl(Book book) 221 | { 222 | var fileType = ConvertType(book.Images.Cover.Type); 223 | return GetBookThumbUrl(book.MediaId, fileType); 224 | } 225 | 226 | /// 227 | /// Constructs the URL for a full-size page image 228 | /// 229 | /// ID of the gallery containing the image 230 | /// Page number of the image 231 | /// File extension of the image 232 | /// Full URL for the specified page image 233 | private string GetPictureUrl(int galleryId, int pageNum, string fileType) 234 | { 235 | return $"{GetGalleryUrl(galleryId)}/{pageNum}.{fileType}"; 236 | } 237 | 238 | /// 239 | /// Constructs the URL for a page's thumbnail image 240 | /// 241 | /// ID of the gallery containing the image 242 | /// Page number of the image 243 | /// File extension of the image 244 | /// Full URL for the specified page's thumbnail 245 | private string GetThumbPictureUrl(int galleryId, int pageNum, string fileType) 246 | { 247 | return $"{GetThumbGalleryUrl(galleryId)}/{pageNum}t.{fileType}"; 248 | } 249 | 250 | /// 251 | /// Constructs the URL for a gallery's cover image 252 | /// 253 | /// ID of the gallery 254 | /// Full URL for the gallery's cover image 255 | private string GetBigCoverUrl(int galleryId) 256 | { 257 | return $"{GetThumbGalleryUrl(galleryId)}/cover.jpg"; 258 | } 259 | 260 | /// 261 | /// Constructs the URL for a page's original full-size image 262 | /// 263 | /// ID of the gallery containing the image 264 | /// Page number of the image 265 | /// Full URL for the original version of the specified page 266 | private string GetOriginPictureUrl(int galleryId, int pageNum) 267 | { 268 | return GetPictureUrl(galleryId, pageNum, "jpg"); 269 | } 270 | 271 | /// 272 | /// Constructs the URL for a gallery's thumbnail image 273 | /// 274 | /// ID of the gallery 275 | /// File extension of the image, defaults to jpg 276 | /// Full URL for the gallery's thumbnail image 277 | private string GetBookThumbUrl(int galleryId, string fileType = "jpg") 278 | { 279 | return $"{GetThumbGalleryUrl(galleryId)}/thumb.{fileType ?? "jpg"}"; 280 | } 281 | 282 | #endregion 283 | 284 | #region Utilities 285 | 286 | /// 287 | /// Retrieves and deserializes JSON data from the specified URL 288 | /// 289 | /// Type to deserialize the JSON into 290 | /// URL to retrieve the JSON from 291 | /// Deserialized object of type TOutput 292 | private async Task GetData(string rootUrl) 293 | { 294 | var json = await _client.GetStreamAsync(rootUrl); 295 | return await JsonSerializer.DeserializeAsync(json, _options); 296 | } 297 | 298 | /// 299 | /// Downloads binary data from the specified URL 300 | /// 301 | /// URL to download from 302 | /// Byte array containing the downloaded data 303 | private async Task GetByteData(string rootUrl) 304 | { 305 | var data = await _client.GetByteArrayAsync(rootUrl); 306 | return data; 307 | } 308 | 309 | /// 310 | /// Gets the image information for a specific page in a book 311 | /// 312 | /// Book containing the image 313 | /// Page number to retrieve (1-based index) 314 | /// Image information for the specified page 315 | /// Thrown when book is null 316 | private Image GetImage(Book book, int pageNum) 317 | { 318 | if (book == null) 319 | throw new ArgumentNullException(nameof(book)); 320 | 321 | var page = book.Images.Pages[pageNum - 1]; 322 | return page; 323 | } 324 | 325 | /// 326 | /// Converts an ImageType enum value to its corresponding file extension 327 | /// 328 | /// ImageType to convert 329 | /// File extension string (without dot) 330 | /// Thrown when the image type is not supported 331 | private string ConvertType(ImageType type) 332 | { 333 | switch (type) 334 | { 335 | case ImageType.Gif: 336 | return "gif"; 337 | case ImageType.Jpg: 338 | return "jpg"; 339 | case ImageType.Png: 340 | return "png"; 341 | default: 342 | throw new NotSupportedException($"Format {nameof(type)} does not support."); 343 | } 344 | } 345 | 346 | #endregion 347 | 348 | #region Search 349 | 350 | /// 351 | /// Retrieves a list of content from the homepage 352 | /// 353 | /// Page number to retrieve 354 | /// Search results containing content from the specified page 355 | public async Task GetHomePageListAsync(int pageNum) 356 | { 357 | var url = GetHomePageUrl(pageNum); 358 | return await GetData(url); 359 | } 360 | 361 | /// 362 | /// Searches for content using the specified keyword 363 | /// 364 | /// Search term to look for 365 | /// Page number of results to retrieve 366 | /// Search results matching the keyword 367 | public async Task GetSearchPageListAsync(string keyword, int pageNum) 368 | { 369 | var url = GetSearchUrl(keyword, pageNum); 370 | return await GetData(url); 371 | } 372 | 373 | /// 374 | /// Retrieves content tagged with a specific tag 375 | /// 376 | /// Tag to filter by 377 | /// How to sort the results 378 | /// Page number to retrieve 379 | /// Search results for content with the specified tag 380 | public async Task GetTagPageListAsync(Tag tag, SortBy sortBy, int pageNum) 381 | { 382 | var url = GetTagUrl(tag, sortBy == SortBy.Popular, pageNum); 383 | return await GetData(url); 384 | } 385 | 386 | #endregion 387 | 388 | #region Books 389 | 390 | /// 391 | /// Retrieves details for a specific book 392 | /// 393 | /// ID of the book to retrieve 394 | /// Book details for the specified ID 395 | public async Task GetBookAsync(int bookId) 396 | { 397 | var url = GetBookDetailsUrl(bookId); 398 | return await GetData(url); 399 | } 400 | 401 | /// 402 | /// Gets recommendations based on a specific book 403 | /// 404 | /// ID of the book to get recommendations for 405 | /// List of recommended books 406 | public async Task GetBookRecommendAsync(int bookId) 407 | { 408 | var url = GetBookRecommendUrl(bookId); 409 | var book = await GetData(url); 410 | return new BookRecommend 411 | { 412 | Result = new List { book } 413 | }; 414 | } 415 | 416 | #endregion 417 | 418 | #region Picture 419 | 420 | /// 421 | /// Downloads a full-size page image 422 | /// 423 | /// Book containing the image 424 | /// Page number to download 425 | /// Byte array containing the image data 426 | public async Task GetPictureAsync(Book book, int pageNum) 427 | { 428 | var url = GetPictureUrl(book, pageNum); 429 | return await GetByteData(url); 430 | } 431 | 432 | /// 433 | /// Downloads a thumbnail image for a page 434 | /// 435 | /// Book containing the image 436 | /// Page number to download 437 | /// Byte array containing the thumbnail data 438 | public async Task GetThumbPictureAsync(Book book, int pageNum) 439 | { 440 | var url = GetThumbPictureUrl(book, pageNum); 441 | return await GetByteData(url); 442 | } 443 | 444 | /// 445 | /// Downloads a book's large cover image 446 | /// 447 | /// Book to get the cover for 448 | /// Byte array containing the cover image data 449 | public async Task GetBigCoverPictureAsync(Book book) 450 | { 451 | var url = GetBigCoverUrl(book.MediaId); 452 | return await GetByteData(url); 453 | } 454 | 455 | /// 456 | /// Downloads an original full-size page image 457 | /// 458 | /// Book containing the image 459 | /// Page number to download 460 | /// Byte array containing the original image data 461 | public async Task GetOriginPictureAsync(Book book, int pageNum) 462 | { 463 | var url = GetOriginPictureUrl(book.MediaId, pageNum); 464 | return await GetByteData(url); 465 | } 466 | 467 | /// 468 | /// Downloads a book's thumbnail image 469 | /// 470 | /// Book to get the thumbnail for 471 | /// Byte array containing the thumbnail data 472 | public async Task GetBookThumbPictureAsync(Book book) 473 | { 474 | var url = GetBookThumbUrl(book); 475 | return await GetByteData(url); 476 | } 477 | 478 | #endregion 479 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NHentaiAPI 2 | 3 | [![Build status](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva?svg=true)](https://ci.appveyor.com/project/SylveonDeko/nhentaiapi) 4 | [![NuGet](https://img.shields.io/nuget/v/NHentaiAPI.svg)](https://www.nuget.org/packages/NHentaiAPI) 5 | [![NuGet](https://img.shields.io/nuget/dt/NHentaiAPI.svg)](https://www.nuget.org/packages/NHentaiAPI) 6 | [![NuGet](https://img.shields.io/badge/月子我婆-passed-ff69b4.svg)](https://github.com/SylveonDeko/NHentaiAPI) 7 | 8 | A full nHentai API implementation for .NET 9 | 10 | ⚠️ If nHentai changes their API format, please create an issue to let me know! 11 | 12 | ## Important Notes 13 | 14 | ### User Agent Requirements 15 | A User-Agent is **required** to use this API. The client will throw an error if none is provided. You can get your User-Agent by: 16 | 1. Going to https://www.whatismybrowser.com/detect/what-is-my-user-agent/ 17 | 2. Or by opening Developer Tools (F12) in your browser, going to Network tab, and looking at the "User-Agent" header in any request 18 | 19 | Example: 20 | ```csharp 21 | var client = new NHentaiClient("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"); 22 | ``` 23 | 24 | ### CSRF Token (If Required) 25 | If nHentai implements CSRF protection, you can get the token by: 26 | 1. Opening Developer Tools (F12) 27 | 2. Going to Network tab 28 | 3. Looking for a request header named 'x-csrf-token' or a cookie named 'csrf_token' 29 | 4. Pass it to the client using the cookies dictionary 30 | 31 | **Important**: The CSRF token must be obtained from the same IP address and User-Agent that will be used with the API. Using a token from a different IP or User-Agent will result in authentication failures. 32 | 33 | ```csharp 34 | var cookies = new Dictionary 35 | { 36 | {"csrf_token", "your-token-here"} 37 | }; 38 | var client = new NHentaiClient("your-user-agent", cookies); 39 | ``` 40 | 41 | ## Features 42 | 43 | ### Search Capabilities: 44 | 45 | 1. Browse homepage content 46 | 2. Search by keywords 47 | 3. Search by tags with optional popularity sorting 48 | 4. Filter tags using `-` prefix (exclusion) 49 | 50 | ### Book Operations: 51 | 52 | 1. Fetch book details 53 | 2. Get related books 54 | 55 | ### Image Operations: 56 | 57 | 1. Page images (preview, thumbnail, and original quality) 58 | 2. Cover images (preview and thumbnail) 59 | 60 | ## Usage Examples 61 | 62 | ### Search Books: 63 | ```csharp 64 | // Initialize client with User-Agent 65 | var client = new NHentaiClient("your-user-agent-string"); 66 | 67 | // Search with filters 68 | var result = await client.GetSearchPageListAsync("school swimsuit full color -loli", 2); 69 | 70 | // Browse homepage 71 | var homeResults = await client.GetHomePageListAsync(1); 72 | ``` 73 | 74 | ### Get Book Details: 75 | ```csharp 76 | var client = new NHentaiClient("your-user-agent-string"); 77 | 78 | // Get book by ID 79 | var book = await client.GetBookAsync(123); 80 | 81 | // Get related books 82 | var related = await client.GetBookRecommendAsync(123); 83 | ``` 84 | 85 | ### Get Images: 86 | ```csharp 87 | var book = await client.GetBookAsync(123); 88 | 89 | // Get full page image 90 | byte[] picture = await client.GetPictureAsync(book, 1); 91 | 92 | // Get cover image 93 | byte[] cover = await client.GetBigCoverPictureAsync(book); 94 | 95 | // Get thumbnails 96 | byte[] thumbnail = await client.GetThumbPictureAsync(book, 1); 97 | byte[] coverThumb = await client.GetBookThumbPictureAsync(book); 98 | ``` 99 | 100 | ## Check Out My Other Projects 101 | - [Mewdeko](https://github.com/SylveonDeko/Mewdeko) - Discord Bot 102 | - [MartineApi](https://github.com/SylveonDeko/MartineApi.Net) - Image API Wrapper 103 | - [NekosBestApi](https://github.com/SylveonDeko/Nekos.Best-API) - Anime Image API 104 | 105 | ## License 106 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | clone_depth: 1 2 | version: '{build}' 3 | skip_tags: true 4 | image: Visual Studio 2022 5 | configuration: Debug 6 | before_build: 7 | - cmd: nuget restore 8 | build: 9 | project: NHentaiAPI.sln 10 | parallel: true 11 | verbosity: minimal --------------------------------------------------------------------------------